/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.startup;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.SessionCookieConfig;
import jakarta.servlet.annotation.HandlesTypes;

import org.apache.catalina.Authenticator;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Globals;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Valve;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.util.ContextName;
import org.apache.catalina.util.Introspection;
import org.apache.jasper.servlet.JasperInitializer;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.Jar;
import org.apache.tomcat.JarScanType;
import org.apache.tomcat.JarScanner;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.bcel.classfile.AnnotationElementValue;
import org.apache.tomcat.util.bcel.classfile.AnnotationEntry;
import org.apache.tomcat.util.bcel.classfile.ArrayElementValue;
import org.apache.tomcat.util.bcel.classfile.ClassFormatException;
import org.apache.tomcat.util.bcel.classfile.ClassParser;
import org.apache.tomcat.util.bcel.classfile.ElementValue;
import org.apache.tomcat.util.bcel.classfile.ElementValuePair;
import org.apache.tomcat.util.bcel.classfile.JavaClass;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.descriptor.InputSourceUtil;
import org.apache.tomcat.util.descriptor.XmlErrorHandler;
import org.apache.tomcat.util.descriptor.web.ContextEjb;
import org.apache.tomcat.util.descriptor.web.ContextEnvironment;
import org.apache.tomcat.util.descriptor.web.ContextLocalEjb;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.apache.tomcat.util.descriptor.web.ContextResourceEnvRef;
import org.apache.tomcat.util.descriptor.web.ContextService;
import org.apache.tomcat.util.descriptor.web.ErrorPage;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.apache.tomcat.util.descriptor.web.FragmentJarScannerCallback;
import org.apache.tomcat.util.descriptor.web.JspPropertyGroup;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import org.apache.tomcat.util.descriptor.web.MessageDestinationRef;
import org.apache.tomcat.util.descriptor.web.MultipartDef;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.apache.tomcat.util.descriptor.web.SecurityRoleRef;
import org.apache.tomcat.util.descriptor.web.ServletDef;
import org.apache.tomcat.util.descriptor.web.SessionConfig;
import org.apache.tomcat.util.descriptor.web.WebXml;
import org.apache.tomcat.util.descriptor.web.WebXmlParser;
import org.apache.tomcat.util.digester.Digester;
import org.apache.tomcat.util.digester.RuleSet;
import org.apache.tomcat.util.file.ConfigFileLoader;
import org.apache.tomcat.util.file.ConfigurationSource;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.scan.JarFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;

/**
 * Startup event listener for a <b>Context</b> that configures the properties
 * of that Context, and the associated defined servlets.
 *
 * @author Craig R. McClanahan
 */
public class ContextConfig implements LifecycleListener {

    private static final Log log = LogFactory.getLog(ContextConfig.class);

    /**
     * The string resources for this package.
     */
    protected static final StringManager sm =
        StringManager.getManager(Constants.Package);


    protected static final LoginConfig DUMMY_LOGIN_CONFIG =
        new LoginConfig("NONE", null, null, null);


    /**
     * The set of Authenticators that we know how to configure.  The key is
     * the name of the implemented authentication method, and the value is
     * the fully qualified Java class name of the corresponding Valve.
     * 封装了Authenticators.properties文件中的key-value值
     */
    protected static final Properties authenticators;

    static {
        // Load our mapping properties for the standard authenticators
        Properties props = new Properties();
        try (InputStream is = ContextConfig.class.getClassLoader().getResourceAsStream(
                "org/apache/catalina/startup/Authenticators.properties")) {
            if (is != null) {
                props.load(is);
            }
        } catch (IOException ioe) {
            props = null;
        }
        authenticators = props;
    }

    /**
     * Deployment count.
     */
    protected static long deploymentCount = 0L;


    /**
     * Cache of default web.xml fragments per Host
     * 每个主机(host)的默认web.xml片段缓存
     */
    protected static final Map<Host,DefaultWebXmlCacheEntry> hostWebXmlCache =
            new ConcurrentHashMap<>();


    /**
     * Set used as the value for {@code JavaClassCacheEntry.sciSet} when there
     * are no SCIs associated with a class.
     */
    private static final Set<ServletContainerInitializer> EMPTY_SCI_SET = Collections.emptySet();


    // ----------------------------------------------------- Instance Variables
    /**
     * Custom mappings of login methods to authenticators
     * 自定义的映射登录身份验证器的方法
     */
    protected Map<String,Authenticator> customAuthenticators;


    /**
     * The Context we are associated with.
     */
    protected volatile Context context = null;


    /**
     * The default web application's deployment descriptor location.
     *  默认web应用程序的部署描述符位置
     */
    protected String defaultWebXml = null;


    /**
     * Track any fatal errors during startup configuration processing.
     */
    protected boolean ok = false;


    /**
     * Original docBase.
     */
    protected String originalDocBase = null;


    /**
     * Anti-locking docBase. It is a path to a copy of the web application
     * in the java.io.tmpdir directory. This path is always an absolute one.
     */
    private File antiLockingDocBase = null;


    /**
     * Map of ServletContainerInitializer to classes they expressed interest in.
     * 用来存储符合条件的ServletContainerInitializer实现类 key:ServletContainerInitializer实现类名称  value为handlesType注解产生的参数类
     */
    protected final Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap =
            new LinkedHashMap<>();

    /**
     * Map of Types to ServletContainerInitializer that are interested in those
     * types.
     * 用来存储ServletContainerInitializer实现类的@hanlesType注解对应的value值  在项目启动的阶段会从spring容器中获取该value的所有实现类作为参数传入到
     * onStartup方法中
     */
    protected final Map<Class<?>, Set<ServletContainerInitializer>> typeInitializerMap =
            new HashMap<>();

    /**
     * Flag that indicates if at least one {@link HandlesTypes} entry is present
     * that represents an annotation.
     * 在@HandlesTypes中的value值最少有一个是注解类型
     */
    protected boolean handlesTypesAnnotations = false;

    /**
     * Flag that indicates if at least one {@link HandlesTypes} entry is present
     * that represents a non-annotation.
     * 在@HandlesTypes中的value值最少有一个是非注解类型
     */
    protected boolean handlesTypesNonAnnotations = false;


    // ------------------------------------------------------------- Properties

    /**
     * Obtain the location of the default deployment descriptor.
     *
     * @return The path to the default web.xml. If not absolute, it is relative
     *         to CATALINA_BASE.
     */
    public String getDefaultWebXml() {
        if (defaultWebXml == null) {
            defaultWebXml = Constants.DefaultWebXml;
        }
        return defaultWebXml;
    }


    /**
     * Set the location of the default deployment descriptor.
     *
     * @param path The path to the default web.xml. If not absolute, it is
     *             relative to CATALINA_BASE.
     */
    public void setDefaultWebXml(String path) {
        this.defaultWebXml = path;
    }


    /**
     * Sets custom mappings of login methods to authenticators.
     *
     * @param customAuthenticators Custom mappings of login methods to
     * authenticators
     */
    public void setCustomAuthenticators(
            Map<String,Authenticator> customAuthenticators) {
        this.customAuthenticators = customAuthenticators;
    }


    // --------------------------------------------------------- Public Methods


    /**
     * Process events for an associated Context.
     *
     * @param event The lifecycle event that has occurred
     */
    @Override
    public void lifecycleEvent(LifecycleEvent event) {

        // Identify the context we are associated with
        try {
            context = (Context) event.getLifecycle();
        } catch (ClassCastException e) {
            log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
            return;
        }

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            //扫描解析Web应用描述文件web.xml，并使用规范将它们(conf/web.xml & conf/web.xml.default & webapps//WEB-INF/web.xml )合并起来,
            //把配置属性转化为Java形式并添加到容器中
            configureStart();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            // 1:如果docBase指向war包，则对war包进行解压 并将解压后的文件夹路径设置成docBase 以提升应用程序性能
            // 2:根据配置做一些预防jar包被锁定的工作；因为Windows系统可能会将某些jar包锁定，从而导致重加载失败；之所以加载失败是因为重加载需要把原来的Web应用完全删除后，
            // 再把新应用重新加载进来，但假如某些jar包被锁定了，就不能删除，除非停止整个Tomcat。
            beforeStart();
        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // Restore docBase for management tools
            //把Context容器的docBase指向另一个目录上，为了解决重新部署时可能导致的锁问题，而把项目复制到另一个目录上，而docBase要指向它。
            if (originalDocBase != null) {
                context.setDocBase(originalDocBase);
            }
        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            //移除Context容器中的相关属性，包括Servl-et、Listener、Filter、欢迎页等
            configureStop();
        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            //创建初始化Degister对象 解析context.xml文件
            init();
        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            //删除工作目录,workDir下的app,workDir=CatalinaBase\Work\Enginname\hostname\appname(默认)
            destroy();
        }

    }


    // -------------------------------------------------------- protected Methods


    /**
     * Process the application classes annotations, if it exists.
     */
    protected void applicationAnnotationsConfig() {

        long t1=System.currentTimeMillis();
        //对三大组件实现类上的注解进行处理
        WebAnnotationSet.loadApplicationAnnotations(context);

        long t2=System.currentTimeMillis();
        // 存储StandardContext的启动时间
        if (context instanceof StandardContext) {
            ((StandardContext) context).setStartupTime(t2-t1+
                    ((StandardContext) context).getStartupTime());
        }
    }


    /**
     * Set up an Authenticator automatically if required, and one has not
     * already been configured.
     * 如果需要，自动设置验证器，但尚未配置
     */
    protected void authenticatorConfig() {

        LoginConfig loginConfig = context.getLoginConfig();
        //loginConfig是空，则默认使用new LoginConfig("NONE", null, null, null)
        if (loginConfig == null) {
            // Need an authenticator to support HttpServletRequest.login()
            loginConfig = DUMMY_LOGIN_CONFIG;
            context.setLoginConfig(loginConfig);
        }

        // Has an authenticator been configured already?
        //如果当前应用已经配置了安全验证value则直接返回
        if (context.getAuthenticator() != null) {
            return;
        }

        // Has a Realm been configured for us to authenticate against?
        //必须给这个context配置realm，没配置则会报错 验证的时候需要从realm中获取授权用户信息跟传递过来的用户信息进行比较
        if (context.getRealm() == null) {
            log.error(sm.getString("contextConfig.missingRealm"));
            ok = false;
            return;
        }

        /*
         * First check to see if there is a custom mapping for the login
         * method. If so, use it. Otherwise, check if there is a mapping in
         * org/apache/catalina/startup/Authenticators.properties.
         */
        Valve authenticator = null;
        //是否自定义了身份验证方法，如果是则使用
        if (customAuthenticators != null) {
            authenticator = (Valve) customAuthenticators.get(loginConfig.getAuthMethod());
        }

        if (authenticator == null) {
            // authenticators static静态块中读取 org/apache/catalina/startup/Authenticators.properties文件得到的properties
            if (authenticators == null) {
                log.error(sm.getString("contextConfig.authenticatorResources"));
                ok = false;
                return;
            }

            // Identify the class name of the Valve we should configure
            //通过该context的loginconfig配置的AuthMethod实例化类Authenticator
            String authenticatorName = authenticators.getProperty(loginConfig.getAuthMethod());
            if (authenticatorName == null) {
                log.error(sm.getString("contextConfig.authenticatorMissing",
                                 loginConfig.getAuthMethod()));
                ok = false;
                return;
            }

            // Instantiate and install an Authenticator of the requested class
            try {
                //通过反射生成class对象，根据class对象中的无参构造函数实例化身份验证类
                Class<?> authenticatorClass = Class.forName(authenticatorName);
                authenticator = (Valve) authenticatorClass.getConstructor().newInstance();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString(
                                    "contextConfig.authenticatorInstantiate",
                                    authenticatorName),
                          t);
                ok = false;
            }
        }
        //添加到valves中，可以看出这个authenticator在处理请求的时候发挥作用
        if (authenticator != null) {
            Pipeline pipeline = context.getPipeline();
            if (pipeline != null) {
                //将身份验证类（其本身实现了value接口）添加到context管道中
                pipeline.addValve(authenticator);
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString(
                                    "contextConfig.authenticatorConfigured",
                                    loginConfig.getAuthMethod()));
                }
            }
        }
    }


    /**
     * Create (if necessary) and return a Digester configured to process the
     * context configuration descriptor for an application.
     * @return the digester for context.xml files
     */
    protected Digester createContextDigester() {
        Digester digester = new Digester();
        //是否对xml文档结构进行类似XSD等类型的校验，默认为fasle。
        digester.setValidating(false);
        //是否进行节点设置规则校验，如果xml中相应节点没有设置解析规则会在控制台显示提示信息。
        digester.setRulesValidation(true);
        //设置无效的属性 即不需要为其提供相应的set方法设置到相应的对象属性中
        Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
        List<String> objectAttrs = new ArrayList<>();
        objectAttrs.add("className");
        fakeAttributes.put(Object.class, objectAttrs);
        // Ignore attribute added by Eclipse for its internal tracking
        List<String> contextAttrs = new ArrayList<>();
        contextAttrs.add("source");
        fakeAttributes.put(StandardContext.class, contextAttrs);
        digester.setFakeAttributes(fakeAttributes);
        //此处两个RuleSet添加了解析<context>标签极其子标签的规则  注意前缀
        RuleSet contextRuleSet = new ContextRuleSet("", false);
        digester.addRuleSet(contextRuleSet);
        RuleSet namingRuleSet = new NamingRuleSet("Context/");
        digester.addRuleSet(namingRuleSet);
        return digester;
    }


    protected boolean getGenerateCode() {
        Catalina catalina = Container.getService(context).getServer().getCatalina();
        if (catalina != null) {
            return catalina.getGenerateCode();
        } else {
            return false;
        }
    }


    protected boolean getUseGeneratedCode() {
        Catalina catalina = Container.getService(context).getServer().getCatalina();
        if (catalina != null) {
            return catalina.getUseGeneratedCode();
        } else {
            return false;
        }
    }


    protected File getGeneratedCodeLocation() {
        Catalina catalina = Container.getService(context).getServer().getCatalina();
        if (catalina != null) {
            return catalina.getGeneratedCodeLocation();
        } else {
            // Cannot happen
            return null;
        }
    }


    protected String getGeneratedCodePackage() {
        Catalina catalina = Container.getService(context).getServer().getCatalina();
        if (catalina != null) {
            return catalina.getGeneratedCodePackage();
        } else {
            return "generatedCodePackage";
        }
    }


    protected static String getContextXmlPackageName(String generatedCodePackage, Container container) {
        StringBuilder result = new StringBuilder();
        Container host = null;
        Container engine = null;
        while (container != null) {
            if (container instanceof Host) {
                host = container;
            } else if (container instanceof Engine) {
                engine = container;
            }
            container = container.getParent();
        }
        result.append(generatedCodePackage);
        if (engine != null) {
            result.append('.');
        }
        if (engine != null) {
            result.append(engine.getName());
            if (host != null) {
                result.append('.');
            }
        }
        if (host != null) {
            result.append(host.getName());
        }
        return result.toString();
    }


    protected File getContextXmlJavaSource(String contextXmlPackageName, String contextXmlSimpleClassName) {
        File generatedSourceFolder = getGeneratedCodeLocation();
        String path = contextXmlPackageName.replace('.', File.separatorChar);
        File packageFolder = new File(generatedSourceFolder, path);
        if (packageFolder.isDirectory() || packageFolder.mkdirs()) {
            return new File(packageFolder, contextXmlSimpleClassName + ".java");
        }
        return null;
    }


    protected void generateClassHeader(Digester digester, String packageName, String resourceName) {
        StringBuilder code = digester.getGeneratedCode();
        code.append("package ").append(packageName).append(';').append(System.lineSeparator());
        code.append("public class ").append(resourceName).append(" implements ");
        code.append(ContextXml.class.getName().replace('$', '.'));
        code.append(" {").append(System.lineSeparator());
        code.append("public void load(");
        code.append(Context.class.getName());
        String contextArgument = digester.toVariableName(context);
        code.append(' ').append(contextArgument).append(") {").append(System.lineSeparator());
        // Create a new variable with the concrete type
        digester.setKnown(context);
        code.append(context.getClass().getName()).append(' ').append(digester.toVariableName(context));
        code.append(" = (").append(context.getClass().getName()).append(") ").append(contextArgument);
        code.append(';').append(System.lineSeparator());
    }


    protected void generateClassFooter(Digester digester) {
        StringBuilder code = digester.getGeneratedCode();
        code.append('}').append(System.lineSeparator());
        code.append('}').append(System.lineSeparator());
    }


    public interface ContextXml {
        public void load(Context context);
    }


    /**
     * Process the default configuration file, if it exists.
     * @param digester The digester that will be used for XML parsing
     */
    protected void contextConfig(Digester digester) {
        //默认的全局配置文件(优先级最低)
        String defaultContextXml = null;
        //这两个属性默认为false,generateCode根据配置文件生成Tomcat内嵌代码,等二次启动部署的时候可以调整UseGeneratedCode属性来使用内嵌代码替代解析配置文件
        boolean generateCode = getGenerateCode();
        boolean useGeneratedCode = getUseGeneratedCode();

        String contextXmlPackageName = null;
        String contextXmlSimpleClassName = null;
        String contextXmlClassName = null;
        File contextXmlJavaSource = null;

        // Open the default context.xml file, if it exists
        if (context instanceof StandardContext) {
            //获取context对象中的defaultContextXml属性信息，默认为null  那什么时候可以对该属性进行设置呢 可在<context>标签中进行设置
            defaultContextXml = ((StandardContext)context).getDefaultContextXml();
        }
        // set the default if we don't have any overrides
        if (defaultContextXml == null) {
            //默认情况都会走该逻辑 conf/context.xml
            defaultContextXml = Constants.DefaultContextXml;
        }

        ContextXml contextXml = null;
        //上下文覆盖标志，默认为false，如果为true则会忽略全局配置文件和host级别默认的配置文件 设置方式跟defaultContextXml属性一样
        if (!context.getOverride()) {

            if (useGeneratedCode || generateCode) {
                contextXmlPackageName = getGeneratedCodePackage();
                contextXmlSimpleClassName = "ContextXmlDefault";
                contextXmlClassName = contextXmlPackageName + "." + contextXmlSimpleClassName;
            }
            if (useGeneratedCode) {
                contextXml = (ContextXml) Digester.loadGeneratedClass(contextXmlClassName);
            }
            if (contextXml != null) {
                contextXml.load(context);
                contextXml = null;
            } else if (!useGeneratedCode) {
                //全局配置文件是否存在 如果存在则将其加载转化为Resource对象，如果不存在则直接跳过
                try (ConfigurationSource.Resource contextXmlResource =
                        ConfigFileLoader.getSource().getResource(defaultContextXml)) {
                    if (generateCode) {
                        contextXmlJavaSource = getContextXmlJavaSource(contextXmlPackageName, contextXmlSimpleClassName);
                        digester.startGeneratingCode();
                        generateClassHeader(digester, contextXmlPackageName, contextXmlSimpleClassName);
                    }
                    URL defaultContextUrl = contextXmlResource.getURI().toURL();
                    //对配置文件进行解析 填充到各自的实例对象中
                    processContextConfig(digester, defaultContextUrl, contextXmlResource.getInputStream());
                    if (generateCode) {
                        generateClassFooter(digester);
                        try (FileWriter writer = new FileWriter(contextXmlJavaSource)) {
                            writer.write(digester.getGeneratedCode().toString());
                        }
                        digester.endGeneratingCode();
                        Digester.addGeneratedClass(contextXmlClassName);
                    }
                } catch (MalformedURLException e) {
                    log.error(sm.getString("contextConfig.badUrl", defaultContextXml), e);
                } catch (IOException e) {
                    // Not found
                }
            }

            if (useGeneratedCode || generateCode) {
                contextXmlPackageName = getContextXmlPackageName(getGeneratedCodePackage(), context);
                contextXmlSimpleClassName = "ContextXmlDefault";
                contextXmlClassName = contextXmlPackageName + "." + contextXmlSimpleClassName;
            }
            if (useGeneratedCode) {
                contextXml = (ContextXml) Digester.loadGeneratedClass(contextXmlClassName);
            }
            if (contextXml != null) {
                contextXml.load(context);
                contextXml = null;
            } else if (!useGeneratedCode) {
                //xmlBase路径下是否存在context.xml.default文件 其是针对整个虚拟主机(host)下的所有应用(context)
                String hostContextFile = Container.getConfigPath(context, Constants.HostContextXml);
                try (ConfigurationSource.Resource contextXmlResource =
                        ConfigFileLoader.getSource().getResource(hostContextFile)) {
                    if (generateCode) {
                        contextXmlJavaSource = getContextXmlJavaSource(contextXmlPackageName, contextXmlSimpleClassName);
                        digester.startGeneratingCode();
                        generateClassHeader(digester, contextXmlPackageName, contextXmlSimpleClassName);
                    }
                    URL defaultContextUrl = contextXmlResource.getURI().toURL();
                    //解析 注意如果属性相同会将全局配置文件设置的进行覆盖
                    processContextConfig(digester, defaultContextUrl, contextXmlResource.getInputStream());
                    if (generateCode) {
                        generateClassFooter(digester);
                        try (FileWriter writer = new FileWriter(contextXmlJavaSource)) {
                            writer.write(digester.getGeneratedCode().toString());
                        }
                        digester.endGeneratingCode();
                        Digester.addGeneratedClass(contextXmlClassName);
                    }
                } catch (MalformedURLException e) {
                    log.error(sm.getString("contextConfig.badUrl", hostContextFile), e);
                } catch (IOException e) {
                    // Not found
                }
            }
        }
        //在创建context对象的时候如果是根据配置文件生成的那么会设置该属性，指定是根据哪个context.xml（应用下的或者xmlBase路径下的）来生成的应用对象
        //默认情况下我们的项目中是没有context.xml文件 所以此处为null
        if (context.getConfigFile() != null) {
            if (useGeneratedCode || generateCode) {
                contextXmlPackageName = getContextXmlPackageName(getGeneratedCodePackage(), context);
                contextXmlSimpleClassName = "ContextXml_" + context.getName().replace('/', '_').replace("-", "__");
                contextXmlClassName = contextXmlPackageName + "." + contextXmlSimpleClassName;
            }
            if (useGeneratedCode) {
                contextXml = (ContextXml) Digester.loadGeneratedClass(contextXmlClassName);
            }
            if (contextXml != null) {
                contextXml.load(context);
                contextXml = null;
            } else if (!useGeneratedCode) {
                if (generateCode) {
                    contextXmlJavaSource = getContextXmlJavaSource(contextXmlPackageName, contextXmlSimpleClassName);
                    digester.startGeneratingCode();
                    generateClassHeader(digester, contextXmlPackageName, contextXmlSimpleClassName);
                }
                //解析该配置文件 会对之前相同的属性重新赋值 其优先级最高
                processContextConfig(digester, context.getConfigFile(), null);
                if (generateCode) {
                    generateClassFooter(digester);
                    try (FileWriter writer = new FileWriter(contextXmlJavaSource)) {
                        writer.write(digester.getGeneratedCode().toString());
                    } catch (IOException e) {
                        // Ignore
                    }
                    digester.endGeneratingCode();
                    Digester.addGeneratedClass(contextXmlClassName);
                }
            }
        }

    }


    /**
     * Process a context.xml.
     * @param digester The digester that will be used for XML parsing
     * @param contextXml The URL to the context.xml configuration
     * @param stream The XML resource stream
     */
    protected void processContextConfig(Digester digester, URL contextXml, InputStream stream) {

        if (log.isDebugEnabled()) {
            log.debug("Processing context [" + context.getName()
                    + "] configuration file [" + contextXml + "]");
        }

        InputSource source = null;

        try {
            source = new InputSource(contextXml.toString());
            //conf/context.xml和context.xml.default时stream不为空  META-INF/context.xml为空 前两者判断是否存在时需要通过加载来判断，加载的过程中生成了流信息
            //而后者是通过context中的属性直接判断是否存在 所以在此处进行了流的加载
            if (stream == null) {
                URLConnection xmlConn = contextXml.openConnection();
                xmlConn.setUseCaches(false);
                stream = xmlConn.getInputStream();
            }
        } catch (Exception e) {
            log.error(sm.getString("contextConfig.contextMissing",
                      contextXml) , e);
        }

        if (source == null) {
            return;
        }

        try {
            //将配置文件的流信息设置到InputSource对象中(SAX接受参数)
            source.setByteStream(stream);
            //获取当前类加载器
            digester.setClassLoader(this.getClass().getClassLoader());
            //设置不使用context类加载器
            digester.setUseContextClassLoader(false);
            //栈中压入父对象
            digester.push(context.getParent());
            digester.push(context);
            XmlErrorHandler errorHandler = new XmlErrorHandler();
            digester.setErrorHandler(errorHandler);
            //对配置文件进行解析
            digester.parse(source);
            if (errorHandler.getWarnings().size() > 0 ||
                    errorHandler.getErrors().size() > 0) {
                errorHandler.logFindings(log, contextXml.toString());
                ok = false;
            }
            if (log.isDebugEnabled()) {
                log.debug("Successfully processed context [" + context.getName()
                        + "] configuration file [" + contextXml + "]");
            }
        } catch (SAXParseException e) {
            log.error(sm.getString("contextConfig.contextParse",
                    context.getName()), e);
            log.error(sm.getString("contextConfig.defaultPosition",
                             "" + e.getLineNumber(),
                             "" + e.getColumnNumber()));
            ok = false;
        } catch (Exception e) {
            log.error(sm.getString("contextConfig.contextParse",
                    context.getName()), e);
            ok = false;
        } finally {
            try {
                if (stream != null) {
                    stream.close();
                }
            } catch (IOException e) {
                log.error(sm.getString("contextConfig.contextClose"), e);
            }
        }
    }


    /**
     * Adjust docBase.
     * @throws IOException cannot access the context base path
     * 更新Context的docBase属性主要是为了满足WAR部署的情况，当Web应用为一个WAR压缩包且需要解压部署（Host的unpackWAR为true,且Context的unpcakWAR为true）时，
     * docBase属性指向的是解压后的文件目录，而非WAR包路径
     *
     * 如果是指向war包将其替换为文件夹的优势：
     * 1、性能优化：解压WAR文件可以提高应用程序的加载和启动性能。解压后的文件夹可以通过文件系统直接读取，而无需解压每个请求。
     * 2、静态资源的访问：将docBase指向解压后的文件夹，使得Tomcat可以直接访问WAR文件中的静态资源（例如HTML、CSS、JavaScript文件等），而无需进行解压操作。
     * 3、热部署：如果您在运行中的Tomcat上更新了WAR文件，Tomcat会自动检测到变化并重新部署应用程序。如果docBase指向的是解压后的文件夹，Tomcat只需覆盖变更的文件，
     * 而不需要重新解压整个WAR文件，从而实现热部署。
     * 总的来说，将docBase指向解压后的文件夹可以提高性能、方便访问静态资源并支持热部署。这是Tomcat默认行为，也是推荐的部署方式
     */
    protected void fixDocBase() throws IOException {

        Host host = (Host) context.getParent();
        // 默认appBase为webapps
        File appBase = host.getAppBaseFile();

        // This could be blank, relative, absolute or canonical
        // 获取context中的docBase属性，注意在context对象创建的时候对其进行了赋值(文件夹或者war包名称)
        String docBaseConfigured = context.getDocBase();
        // If there is no explicit docBase, derive it from the path and version
        //如果没有指定docBase,那么根据其path(访问路径)和version(版本)进行推断
        if (docBaseConfigured == null) {
            // Trying to guess the docBase according to the path
            //尝试获取context中的path属性，如果其也为null，那么直接返回
            String path = context.getPath();
            if (path == null) {
                return;
            }
            //根据path，version封装成contextName对象，封装的过程中会推断出baseName值
            ContextName cn = new ContextName(path, context.getWebappVersion());
            //将推断出的war包名称或者文件夹名称作为docBase
            docBaseConfigured = cn.getBaseName();
        }

        // Obtain the absolute docBase in String and File form
        // 绝对路径变量,具体到某个文件夹或者war包
        String docBaseAbsolute;
        File docBaseConfiguredFile = new File(docBaseConfigured);
        //如果docBase不是绝对路径，那么将其跟webapps路径进行拼接形成绝对路径，否则直接得到绝对路径
        if (!docBaseConfiguredFile.isAbsolute()) {
            docBaseAbsolute = (new File(appBase, docBaseConfigured)).getAbsolutePath();
        } else {
            docBaseAbsolute = docBaseConfiguredFile.getAbsolutePath();
        }
        //构建绝对路径下的file对象
        File docBaseAbsoluteFile = new File(docBaseAbsolute);
        //将原始的docBase值进行暂存，之后docBase可能会发生改变 （G:\tomcat\apache-tomcat-10.1.2-src\home\webapps\test#test1.war）
        String originalDocBase = docBaseAbsolute;
        //通过path和version构造出新的contextName属性，并且将其拼接组装的baseName赋值给pathName
        ContextName cn = new ContextName(context.getPath(), context.getWebappVersion());
        //war包或者文件夹的名称
        String pathName = cn.getBaseName();

        boolean unpackWARs = true;
        // 只要StandardHost或StandardContext中任意一个unpackWARs为false ，将不进行war的解压
        if (host instanceof StandardHost) {
            unpackWARs = ((StandardHost) host).isUnpackWARs();
            if (unpackWARs && context instanceof StandardContext) {
                unpackWARs =  ((StandardContext) context).getUnpackWAR();
            }
        }

        // At this point we need to determine if we have a WAR file in the
        // appBase that needs to be expanded. Therefore we consider the absolute
        // docBase NOT the canonical docBase. This is because some users symlink
        // WAR files into the appBase and we want this to work correctly.
        // 此时，我们需要确定appBase中是否有需要扩展的WAR文件。因此，我们认为绝对的docBase不是规范的docBase。
        // 这是因为一些用户将WAR文件符号链接到appBase中，我们希望它能够正常工作。
        // 该属性是判断docBase对应的路径是否在appBase(webapps)路径下
        boolean docBaseAbsoluteInAppBase = docBaseAbsolute.startsWith(appBase.getPath() + File.separatorChar);
        //如果指定的路径是具体到某个war而不是文件夹【正常的war包（内部或者外部）部署时默认走该逻辑】
        if (docBaseAbsolute.toLowerCase(Locale.ENGLISH).endsWith(".war") && !docBaseAbsoluteFile.isDirectory()) {
            //将对应的docBase文件对象封装成URL(通过URL连接，我们就可以确定资源的位置,就可以经由URL完成读取和修改数据的操作)
            URL war = UriUtil.buildJarUrl(docBaseAbsoluteFile);
            // 如果允许解压
            if (unpackWARs) {
                // 解压war包 并返回解压后的文件夹的绝对路径（G:\tomcat\apache-tomcat-10.1.2-src\home\webapps\test#test1）
                // 如果是外部war包也会在webapps下生成一个跟war包同名的文件夹
                docBaseAbsolute = ExpandWar.expand(host, war, pathName);
                docBaseAbsoluteFile = new File(docBaseAbsolute);
                if (context instanceof StandardContext) {
                    // 保存之前的docBase  xxx.war样式
                    ((StandardContext) context).setOriginalDocBase(originalDocBase);
                }
            } else {
                // 如果不需要解压，则验证war包
                ExpandWar.validate(host, war, pathName);
            }
        //主要用来处理使用xmlBase部署描述符文件时 <context>属性中docBase指向的为webapps下的文件夹  此时有可能文件夹和对应war包都存在
        } else {
            // 如果docBase为一个有效的目录，而且存在与该目录同名的WAR包，同时需要解压部署，则重新解压WAR包
            File docBaseAbsoluteFileWar = new File(docBaseAbsolute + ".war");
            URL war = null;
            //同名的war包存在，且属于webapps路径下
            if (docBaseAbsoluteFileWar.exists() && docBaseAbsoluteInAppBase) {
                war = UriUtil.buildJarUrl(docBaseAbsoluteFileWar);
            }
            //指定的文件夹对象存在，那么只需要直接解压即可
            if (docBaseAbsoluteFile.exists()) {
                //war不为null 那么说明docBaseAbsoluteInAppBase为true 那么docBaseAbsolute在webapps路径下 同时docBaseAbsoluteFile是docBaseAbsolute
                //的file对象说明该有效目录也在webapps下 那么直接解压即可
                if (war != null && unpackWARs) {
                    // Check if WAR needs to be re-expanded (e.g. if it has
                    // changed). Note: HostConfig.deployWar() takes care of
                    // ensuring that the correct XML file is used.
                    // This will be a NO-OP if the WAR is unchanged.
                    ExpandWar.expand(host, war, pathName);
                }
            } else {
                //指定的文件夹不存在但是同名的war包存在
                if (war != null) {
                    // 如果docBase为一个有效目录，而且存在与该目录同名的WAR包，同时需要解压部署
                    if (unpackWARs) {
                        //解压WAR文件
                        docBaseAbsolute = ExpandWar.expand(host, war, pathName);
                        docBaseAbsoluteFile = new File(docBaseAbsolute);
                    } else {
                        //如果不需要解压部署，只检测WAR包，docBase为WAR包路径
                        docBaseAbsoluteFile = docBaseAbsoluteFileWar;
                        ExpandWar.validate(host, war, pathName);
                    }
                }
                if (context instanceof StandardContext) {
                    ((StandardContext) context).setOriginalDocBase(originalDocBase);
                }
            }
        }
        //获取docBaseAbsoluteFile的规范路径
        String docBaseCanonical = docBaseAbsoluteFile.getCanonicalPath();

        // Re-calculate now docBase is a canonical path
        boolean docBaseCanonicalInAppBase =
                docBaseAbsoluteFile.getCanonicalFile().toPath().startsWith(appBase.toPath());
        String docBase;
        //是否在webapps下，如果是的话则将webapps前面的路径截取掉，只保留webapps之后的路径即可
        if (docBaseCanonicalInAppBase) {
            docBase = docBaseCanonical.substring(appBase.getPath().length());
            docBase = docBase.replace(File.separatorChar, '/');
            if (docBase.startsWith("/")) {
                docBase = docBase.substring(1);
            }
        } else {
            docBase = docBaseCanonical.replace(File.separatorChar, '/');
        }
        //对docBase重新赋值
        context.setDocBase(docBase);
    }

    // 将Web应用项目根据部署次数重命名并复制到%CATALIN_HOME%/temp临时目录下（例如第一次部署就是1-myTomcat.war）,并把Context对象的docBase 指向临时目录下的Web项目，
    // 这样每次重新热部署都有一个新的应用名， 就算原来应用的某些Jar包被锁定也不会导致部署失败。具体解决方法是在元素中增加一个属性antiResourceLocking=“true” 默认是"false"。这样就可以热部署了。
    // antiResourceLocking为true有几个副作用：
    //1:会延长应用的启动时间，因为多了临时目录的清理和往临时目录拷贝应用内容的操作；
    //2:如果不知道这个属性的原理，修改webapps下应用的JSP，那就不会动态重加载到新的页面内容了，因为应用的docbase已经不再在webapps下了；
    //3:停止Tomcat的时候，临时目录下实际的docbase会被删掉，结合第二条和第三条，如果要修改应用的JSP，那必须将改动同时拷贝到两个目录下（原始docbase和临时目录下的docbase）。
    // 所以Tomcat里这个属性缺省为false
    protected void antiLocking() {
        if ((context instanceof StandardContext)
               // 当Context的antiResourceLocking属性为true时，Tomcat会将当前的Web应用目录复制到临时文件夹下，以避免对原目录资源加锁。
                && ((StandardContext) context).getAntiResourceLocking()) {
            // 根据Host的appBase以及Context 的docBase计算docBase的绝对路径
            Host host = (Host) context.getParent();
            String docBase = context.getDocBase();
            if (docBase == null) {
                return;
            }
            //原始的docBase路径
            originalDocBase = docBase;

            File docBaseFile = new File(docBase);
            if (!docBaseFile.isAbsolute()) {
                docBaseFile = new File(host.getAppBaseFile(), docBase);
            }

            String path = context.getPath();
            if (path == null) {
                return;
            }
            ContextName cn = new ContextName(path, context.getWebappVersion());
            docBase = cn.getBaseName();
            //java.io.tmpdir默认指向Tomcat的temp目录 形如：C:\Users\Lenovo\AppData\Local\Temp\
            String tmp = System.getProperty("java.io.tmpdir");
            File tmpFile = new File(tmp);
            if (!tmpFile.isDirectory()) {
                log.error(sm.getString("contextConfig.noAntiLocking", tmp, context.getName()));
                return;
            }
            // 2. 计算临时文件夹的Web应用根目录或WAR包名
            // 2.1 Web目录：${Context生命周期内的部署次数}-${目录名}
            // 2.2 WAR包：${Context生命周期内部署次数}-${WAR包名}
            if (originalDocBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
                antiLockingDocBase = new File(tmpFile, deploymentCount++ + "-" + docBase + ".war");
            } else {
                antiLockingDocBase = new File(tmpFile, deploymentCount++ + "-" + docBase);
            }
            antiLockingDocBase = antiLockingDocBase.getAbsoluteFile();

            if (log.isDebugEnabled()) {
                log.debug("Anti locking context[" + context.getName()
                        + "] setting docBase to " +
                        antiLockingDocBase.getPath());
            }

            // Cleanup just in case an old deployment is lying around
            ExpandWar.delete(antiLockingDocBase);
            // 制Web目录或者WAR包到临时目录
            if (ExpandWar.copy(docBaseFile, antiLockingDocBase)) {
                // 将Context的docBase更新为临时目录下的Web应用目录或者WAR包路径
                context.setDocBase(antiLockingDocBase.getPath());
            }
        }
    }


    /**
     * Process a "init" event for this Context.
     * 1:创建用于指定解析规则Digester对象，主要工作有两部分，一是解析默认配置文件(conf以及enginName/HostName)，二是解析节点；因为在HostConfig监听器中
     * 只是根据节点属性创建了一个Context对象，但Context子节点的属性并没有解析和设置到Context对象中。
     * 2:用创建的Digester对象按顺序解析conf/context.xml、conf/[EngineName]/[HostName]/context.xml.default、/META-INF/context.xml
     * (如果copyXML属性为true，那么解析的是xmlBase下的xml文件，此时再调整项目内的xml文件是不起作用的)等文件，必须按照这个顺序，这种顺序保证了特定属性值可以覆盖默认属性值。
     */
    protected synchronized void init() {
        // Called from StandardContext.init()

        Digester contextDigester = null;
        //使用生成的代码替代配置文件 默认为false 配置文件在解析的时候会将属性信息通过代码的方式自动生成一个类，之后就可以调整该属性直接使用生成的代码来替代配置文件
        if (!getUseGeneratedCode()) {
            //创建初始化digester对象并对<context>标签的解析规则进行设置
            contextDigester = createContextDigester();
            //创建SAXParser对象，用来解析文件流
            contextDigester.getParser();
        }

        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.init"));
        }
        //为此上下文(context)设置“正确配置”标志。检测到致命配置错误时启动侦听器可以将此设置为false，以避免应用程序可用
        context.setConfigured(false);
        //跟踪 启动配置处理过程中的任何致命错误
        ok = true;
        //通过解析配置文件来对context对象进行属性填充
        contextConfig(contextDigester);
    }


    /**
     * Process a "before start" event for this Context.
     */
    protected synchronized void beforeStart() {

        try {
            // 重新设置docBase，如果docBase指向的是一个具体的war[tomcat内部的或者外部的]，unpackWAR为true(默认)，那么将其进行解压且docbase重新指向文件夹
            fixDocBase();
        } catch (IOException e) {
            log.error(sm.getString(
                    "contextConfig.fixDocBase", context.getName()), e);
        }
        // 防止资源锁（默认不开启，耗费性能）
        //背景：Tomcat应用更新时，把新的WAR包放到webapps目录下，Tomcat就会自动把原来的同名webapp删除，并把WAR包解压，运行新的webapp。
        //但是，有时候Tomcat并不能把旧的webapp完全删除，通常会留下WEB-INF/lib下的某个jar包，必须关闭Tomcat结束jar包依赖才能删除，这就导致自动部署失败
        // 为什么某些war包会删除不掉：
        // 1、类加载器缓存机制：Tomcat使用类加载器加载Web应用程序的类和资源。类加载器将加载的类和资源缓存在内存中，以提高性能。当一个Web应用程序被部署并加载后，
        // 其中的类和资源会被缓存在类加载器的内部缓存中。即使停止或删除了Web应用程序，该类加载器的缓存可能仍然持有对一些类或资源的引用，导致相关的文件无法被完全删除。
        // 2、操作系统文件锁定：某些操作系统可能在文件被使用时对其进行锁定，防止其他程序对其进行修改或删除。在某些情况下，Tomcat无法释放对某些文件或目录的锁定，
        // 导致无法删除这些文件或目录
        antiLocking();
    }


    /**
     * Process a "contextConfig" event for this Context.
     */
    protected synchronized void configureStart() {
        // Called from StandardContext.start()

        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.start"));
        }

        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.xmlSettings",
                    context.getName(),
                    Boolean.valueOf(context.getXmlValidation()),
                    Boolean.valueOf(context.getXmlNamespaceAware())));
        }
        //解析web.xml,包括注解式  servlet3.0之前,都是将servlet配置在web.xml文件中,servlet3.0之后,出现了注解的方式以及web-fragment.xml片段
        webConfig();

        //20221230增加，为了解决无法为JSP编译类(为了使源码正常运行，自增代码)
        context.addServletContainerInitializer(new JasperInitializer(),null);

        //ignoreAnnotations是否忽略Listener，Filter,Servlet实现类上的注解 默认为false 其值为webxml.isMetadataComplete()
        if (!context.getIgnoreAnnotations()) {
            //对Listener，Filter,Servlet实现类上的注解进行解析处理(Resources 、 Resource 、EJB、WebServiceRef和DeclareRoles等)
            //servlet组件的处理多出来@runas和@ServletSecurity
            applicationAnnotationsConfig();
        }
        if (ok) {
            //<security-constraint>标签验证处理
            validateSecurityRoles();
        }

        // Configure an authenticator if we need one
        if (ok) {
            //<login-config>标签的处理，根据其属性信息来实例化Authenticator接口的实现类并且添加到管道中从而进行身份验证（注意该阀值是添加到了context级别的pipline）
            authenticatorConfig();
        }

        // Dump the contents of this pipeline if requested
        //将context容器管道中的value进行打印
        if (log.isDebugEnabled()) {
            log.debug("Pipeline Configuration:");
            Pipeline pipeline = context.getPipeline();
            Valve valves[] = null;
            if (pipeline != null) {
                valves = pipeline.getValves();
            }
            if (valves != null) {
                for (Valve valve : valves) {
                    log.debug("  " + valve.getClass().getName());
                }
            }
            log.debug("======================");
        }

        // Make our application available if no problems were encountered
        if (ok) {
            //如果执行到此处没有发生异常，那么就将configured属性设置为true，代表当前context可用
            context.setConfigured(true);
        } else {
            log.error(sm.getString("contextConfig.unavailable"));
            context.setConfigured(false);
        }

    }


    /**
     * Process a "stop" event for this Context.
     */
    protected synchronized void configureStop() {

        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.stop"));
        }

        int i;

        // Removing children
        Container[] children = context.findChildren();
        for (i = 0; i < children.length; i++) {
            context.removeChild(children[i]);
        }

        // Removing application parameters
        /*
        ApplicationParameter[] applicationParameters =
            context.findApplicationParameters();
        for (i = 0; i < applicationParameters.length; i++) {
            context.removeApplicationParameter
                (applicationParameters[i].getName());
        }
        */

        // Removing security constraints
        SecurityConstraint[] securityConstraints = context.findConstraints();
        for (i = 0; i < securityConstraints.length; i++) {
            context.removeConstraint(securityConstraints[i]);
        }

        // Removing errors pages
        ErrorPage[] errorPages = context.findErrorPages();
        for (i = 0; i < errorPages.length; i++) {
            context.removeErrorPage(errorPages[i]);
        }

        // Removing filter defs
        FilterDef[] filterDefs = context.findFilterDefs();
        for (i = 0; i < filterDefs.length; i++) {
            context.removeFilterDef(filterDefs[i]);
        }

        // Removing filter maps
        FilterMap[] filterMaps = context.findFilterMaps();
        for (i = 0; i < filterMaps.length; i++) {
            context.removeFilterMap(filterMaps[i]);
        }

        // Removing Mime mappings
        String[] mimeMappings = context.findMimeMappings();
        for (i = 0; i < mimeMappings.length; i++) {
            context.removeMimeMapping(mimeMappings[i]);
        }

        // Removing parameters
        String[] parameters = context.findParameters();
        for (i = 0; i < parameters.length; i++) {
            context.removeParameter(parameters[i]);
        }

        // Removing security role
        String[] securityRoles = context.findSecurityRoles();
        for (i = 0; i < securityRoles.length; i++) {
            context.removeSecurityRole(securityRoles[i]);
        }

        // Removing servlet mappings
        String[] servletMappings = context.findServletMappings();
        for (i = 0; i < servletMappings.length; i++) {
            context.removeServletMapping(servletMappings[i]);
        }

        // Removing welcome files
        String[] welcomeFiles = context.findWelcomeFiles();
        for (i = 0; i < welcomeFiles.length; i++) {
            context.removeWelcomeFile(welcomeFiles[i]);
        }

        // Removing wrapper lifecycles
        String[] wrapperLifecycles = context.findWrapperLifecycles();
        for (i = 0; i < wrapperLifecycles.length; i++) {
            context.removeWrapperLifecycle(wrapperLifecycles[i]);
        }

        // Removing wrapper listeners
        String[] wrapperListeners = context.findWrapperListeners();
        for (i = 0; i < wrapperListeners.length; i++) {
            context.removeWrapperListener(wrapperListeners[i]);
        }

        // Remove (partially) folders and files created by antiLocking
        if (antiLockingDocBase != null) {
            // No need to log failure - it is expected in this case
            ExpandWar.delete(antiLockingDocBase, false);
        }

        // Reset ServletContextInitializer scanning
        initializerClassMap.clear();
        typeInitializerMap.clear();

        ok = true;

    }


    /**
     * Process a "destroy" event for this Context.
     */
    protected synchronized void destroy() {
        // Called from StandardContext.destroy()
        if (log.isDebugEnabled()) {
            log.debug(sm.getString("contextConfig.destroy"));
        }

        // Skip clearing the work directory if Tomcat is being shutdown
        Server s = getServer();
        if (s != null && !s.getState().isAvailable()) {
            return;
        }

        // Changed to getWorkPath per Bugzilla 35819.
        if (context instanceof StandardContext) {
            String workDir = ((StandardContext) context).getWorkPath();
            if (workDir != null) {
                ExpandWar.delete(new File(workDir));
            }
        }
    }


    private Server getServer() {
        Container c = context;
        while (c != null && !(c instanceof Engine)) {
            c = c.getParent();
        }

        if (c == null) {
            return null;
        }

        Service s = ((Engine)c).getService();

        if (s == null) {
            return null;
        }

        return s.getServer();
    }

    /**
     * Validate the usage of security role names in the web application
     * deployment descriptor.  If any problems are found, issue warning
     * messages (for backwards compatibility) and add the missing roles.
     * 验证web应用程序部署描述符中安全角色名称的用法。如果发现任何问题，请发出警告消息（为了向后兼容）并添加缺少的角色
     * (To make these problems fatal instead, simply set the <code>ok</code>
     * instance variable to <code>false</code> as well).
     */
    protected void validateSecurityRoles() {

        // Check role names used in <security-constraint> elements
        //Constraints，webConfig方法step9或者servlet上@ServletSecurity处理得到
        SecurityConstraint constraints[] = context.findConstraints();
        for (SecurityConstraint constraint : constraints) {
            String roles[] = constraint.findAuthRoles();
            //处理<security-constraint>配置的role
            for (String role : roles) {
                //如果<security-constraint>配置的role不等于*并且不在SecurityRoles(定义应用安全角色<security-role>)中，调用context.addSecurityRole
                if (!"*".equals(role) &&
                        !context.findSecurityRole(role)) {
                    log.warn(sm.getString("contextConfig.role.auth", role));
                    context.addSecurityRole(role);
                }
            }
        }

        // Check role names used in <servlet> elements
        //处理<servlet>配置的role
        Container wrappers[] = context.findChildren();
        for (Container container : wrappers) {
            Wrapper wrapper = (Wrapper) container;
            //@runAs 或者 <run-as>
            String runAs = wrapper.getRunAs();
            if ((runAs != null) && !context.findSecurityRole(runAs)) {
                log.warn(sm.getString("contextConfig.role.runas", runAs));
                context.addSecurityRole(runAs);
            }
            //<security-role-ref>: Web应用中使用的安全角色的引用 的处理 （tomcat-user.xml）
            String names[] = wrapper.findSecurityReferences();
            for (String name : names) {
                String link = wrapper.findSecurityReference(name);
                if ((link != null) && !context.findSecurityRole(link)) {
                    log.warn(sm.getString("contextConfig.role.link", link));
                    context.addSecurityRole(link);
                }
            }
        }

    }


    protected File getHostConfigBase() {
        File file = null;
        if (context.getParent() instanceof Host) {
            file = ((Host)context.getParent()).getConfigBaseFile();
        }
        return file;
    }

    /**
     * 合并顺序，以web-inf/web.xml为主，合并的越晚优先级越低
     * web.xml->web-fragment.xml->tomcat-web.xml->default.xml(conf/web.xml,web.xml.default 两者以后者host级别的为主最终形成default.xml)
     * Scan the web.xml files that apply to the web application and merge them
     * using the rules defined in the spec. For the global web.xml files,
     * where there is duplicate configuration, the most specific level wins. ie
     * an application's web.xml takes precedence over the host level or global
     * web.xml file.
     * 1:将Context级别的web.xml文件解析复制到WebXml对象中
     * 2:扫描Web应用/WEB-INF/lib目录下的所有Jar包里面的/META-INF/web-fragment.xml文件生成多个Web Fragment，Web Fragment本质上还是WebXml对象
     * 3:将这些Web Fragment根据Servlet规范进行排序，主要是根据绝对顺序和相对顺序
     * 4:根据Web Fragment扫描每个Jar包中的ServletContainerInitializer，把他们添加到Context容器的一个Map中，方便Context容器初始化时调用它们
     * 5:解析Web应用/WEB-INF/classes目录下被注解的Servlet、Filter、或Listener，对应的注解符为@WebServlet、@WebFilter、@WebListener
     * 6:解析Web应用相关jar包里的注解，同样是Servlet、Filter、Listener
     * 7:合并Web Fragment到总的WebXml对象中
     * 8:合并默认web.xml对应的WebXml对象到总的WebXml对象中，默认的web.xml包含了Web容器全局web.xml、Host容器级别的web.xml，路径分别对应Tomcat目录的/cong/web.xml文件和conf/engine’sname/host’s nam-e/web.xml.default文件
     * 9:将有些使用了定义的Servlet转换为JSPServlet
     * 10:将WebXml对象中的上下文参数、EJB、环境变量、错误页面、Filter、监听器、安全认证、会话配置、标签、欢迎页、JSP属性组等设置到Context容器中
     * 11:扫描每个Jar包的/META-INF/resources/目录，将此目录下的静态资源添加到Context容器中
     * 12:将ServletContainerInitializer添加到Context容器中
     */
    protected void webConfig() {
        /*
         * Anything and everything can override the global and host defaults.
         * This is implemented in two parts
         * - Handle as a web fragment that gets added after everything else so
         *   everything else takes priority
         * - Mark Servlets as overridable so SCI configuration can replace
         *   configuration from the defaults
         */

        /*
         * The rules for annotation scanning are not as clear-cut as one might
         * think. Tomcat implements the following process:
         * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
         *   which Servlet spec version is declared in web.xml. The EG has
         *   confirmed this is the expected behaviour.
         * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
         *   web.xml is marked as metadata-complete, JARs are still processed
         *   for SCIs.
         * - If metadata-complete=true and an absolute ordering is specified,
         *   JARs excluded from the ordering are also excluded from the SCI
         *   processing.
         * - If an SCI has a @HandlesType annotation then all classes (except
         *   those in JARs excluded from an absolute ordering) need to be
         *   scanned to check if they match.
         *  如果SCI具有@HandlesType注释，则需要扫描所有类（JAR中排除在绝对排序之外的类除外）以检查它们是否匹配。sprigMVC的spi扩展机制就是利用该功能
         */
        //初始化xml解析器(里面封装了两个digester，分别解析web.xml和web-fragment.xml:模块化配置文件)，创建对应的digester对象以及为其设置相应的解析规则等信息
        WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                context.getXmlValidation(), context.getXmlBlockExternal());

        //解析conf/web.xml和xmlBase下host层面的web.xml.default 两个全局的配置文件 需要注意两者会把属性初始化到同一个WebXml对象中，所以定义相同的属性会进行覆盖
        //host层面的优先级更高
        Set<WebXml> defaults = new HashSet<>();
        defaults.add(getDefaultWebXmlFragment(webXmlParser));

        //解析/WEB-INF/tomcat-web.xml配置文件(项目中的) 将其单独初始化到一个新的WebXml对象中
        Set<WebXml> tomcatWebXml = new HashSet<>();
        tomcatWebXml.add(getTomcatWebXmlFragment(webXmlParser));

        //创建WebXml对象来封装项目中的web.xml配置文件信息
        WebXml webXml = createWebXml();

        // Parse context level web.xml
        // 读取/WEB-INF/web.xml，生成对应的文件流
        InputSource contextWebXml = getContextWebXmlSource();
        // /WEB-INF/web.xml文件的内容解析到webXml里面
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }
        //获取当前应用的上下文(多个servlet之间的共享区域)
        ServletContext sContext = context.getServletContext();

        // Ordering is important here
        // 处理web-fragment.xml文件,web-fragment.xml:servlet 3.0可以将配置文件分散在多个jar包里面，而且还可以定义这些配置文件的顺序。分为绝对顺序和相对顺序，
        // 绝对顺序是通过absolute-ordering标签定义的：
        //  <web-app>
        //        <name>...</name>
        //        <absolute-ordering>
        //            <name>fragment1</name>
        //            <name>fragment2</name>
        //        </absolute-ordering>
        //    </web-app>
        // 还可以在web-fragment.xml里面通过before,after标签来定义这些配置文件的先后顺序，将这些配置文件加到集合orderedFragments中

        // Step 1. Identify all the JARs packaged with the application and those
        // provided by the container. If any of the application JARs have a
        // web-fragment.xml it will be parsed at this point. web-fragment.xml
        // files are ignored for container provided JARs.
        //Step 1. 扫描/WEB-INF/lib目录下的jar文件，如果在META-INF下含有web-fragment.xml文件，解析它
        // 注意这里传入的WebXml对象中已经封装了该应用的web.xml数据
        Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

        // Step 2. Order the fragments.
        //Step 2. 根据web.xml中<absolute-ordering>标签以及片段自身定义的<before>,<after>标签，对片段web-fragment.xml进行排序
        Set<WebXml> orderedFragments = null;
        // 将web-fragment.xml创建的WebXml对象按照Servlet规范进行排序，同时将排序结果对应的JAR文件名列表设置到ServletContext属性中
        // 属性名为javax.servlet.context.orderedLibs，该排序非常重要，因为这决定了Filter等执行顺序 。
        // 【注意】：尽管Servlet规范定义了web-fragment.xml的排序（绝对排序和相对排序），但是为了降低各个模块的耦合度，Web应用在定义web-fragment.xml
        // 时应尽量保证相对独立性，减少相互间的依赖，将产生依赖过多的配置尝试放到web.xml中
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);

        //执行到此处时fragments和orderedFragments中的数据量是一致的，只不过后者进行了排序 两者承载的数据为当前应用所依赖的所有jar

        // Step 3. Look for ServletContainerInitializer implementations
        //Step 3.查找并创建ServletContainerInitializer的实现类
        if (ok) {
            processServletContainerInitializers();
        }
        //metadataComplete元数据是否完整，web.xml中的<metadata-complete>标签 默认为false
        //typeInitializerMap 当前项目引用的jar包中存在符合条件的ServletContainerInitializer实现类，并且该类上存在@HandlesType注解
        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // Steps 4 & 5.
            //Steps 4:检测javax.servlet.annotation.HandlesTypes注解
            // 当WebXml的metadataComplete为false, 查找当前应用下的class是否使用javax.servlet.annotation.WebServlet ，javax.servlet
            // .annotation.WebFilter javax.servlet.annotation.WebListener注解配置，如果使用则将其属性信息合并到WebXml
            //Steps 5:处理JAR包内的注解，只处理包含web-fragment.xml的JAR,对于JAR包中的每个类做如下处理。检测javax.servlet.annotation.HandlesTypes注解
            // 当 "主WebXml"和片段"WebXml"的metadataComplete均为false,查找javax.servlet.annotation.WebServlet,javax.servlet.annotation.WebFilter
            // javax.servlet.annotation.WebListener注解配置，将其合并到"片段WebXml"
            processClasses(webXml, orderedFragments);
        }
        // metadataComplete元数据是否完整,其完整性是指是否拆分出了web-flagment.xml片段，而不代表其内容上的完整性
        //只要web.xml中配置了metadata-complete=“true”，则丢弃扫描到jar包的web-fragment.xml及jar包的@WebServlet , @WebFilter
        // 以及@WebListener注解。以及项目内的@WebServlet也会进行丢弃
        if (!webXml.isMetadataComplete()) {
            // Step 6. Merge web-fragment.xml files into the main web.xml
            // file.
            // Step 6. 将应用中的web.xml与orderedFragments进行合并，合并在WebXml类的merge方法中实现
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }
            // Step 7. 将应用中的web.xml与tomcat-web.xml合并 再与全局的web.xml文件（conf/web.xml和web.xml.default）进行合并
            // Step 7a
            // merge tomcat-web.xml
            webXml.merge(tomcatWebXml);

            // Step 7b. Apply global defaults
            // Have to merge defaults before JSP conversion since defaults
            // provide JSP servlet definition.
            //配置JSP Servlet(conf/web.xml中定义了该servlet)
            webXml.merge(defaults);

            // Step 8. Convert explicitly mentioned jsps to servlets
            if (ok) {
                // JspFile属性的作用是将Servlet与指定的JSP文件进行关联，以便在请求到达Servlet时能够转发到相应的JSP文件进行处理和生成响应
                // 配置JspServlet，对于当前Web应用中JspFile属性不为空的Servlet，将其servletClass设置为org.apache.jsper.servlet.JspServlet(Tomcat提供了JSP引擎)，
                // 同时将JspFile设置到Servlet初始化参数集合中且ServletDef中的JspFile属性置空
                convertJsps(webXml);
            }

            // Step 9. Apply merged web.xml to Context
            //Step 9. 使用"主WebXml"配置当前StandardContext，包括Servlet,Filter,Listener等Servlet规范中支持的组件，对于ServletContext
            // 层级对象，直接由StandardContext维护，对于Servlet,则创建StandardWrapper子对象，并添加到StandardContext实例。
            if (ok) {
                configureContext(webXml);
            }
        } else {
            webXml.merge(tomcatWebXml);
            webXml.merge(defaults);
            convertJsps(webXml);
            configureContext(webXml);
        }

        if (context.getLogEffectiveWebXml()) {
            log.info(sm.getString("contextConfig.effectiveWebXml", webXml.toXml()));
        }

        // Always need to look for static resources
        // Step 10. Look for static resources packaged in JARs
        //Step 10. 查找JAR包的"META-INF/resource/"下的静态资源，并添加到StandardContext中
        if (ok) {
            // Spec does not define an order.
            // Use ordered JARs followed by remaining JARs
            Set<WebXml> resourceJars = new LinkedHashSet<>(orderedFragments);
            for (WebXml fragment : fragments.values()) {
                if (!resourceJars.contains(fragment)) {
                    resourceJars.add(fragment);
                }
            }
            // 加载WEB-INF/lib/xxx.jar/META-INF/resources/的静态资源
            processResourceJARs(resourceJars);
            // See also StandardContext.resourcesStart() for
            // WEB-INF/classes/META-INF/resources configuration
        }

        // Step 11. Apply the ServletContainerInitializer config to the
        // context
        if (ok) {
            // 将ServletContainerInitializer扫描结果添加到StandardContext的initializers属性中，以便StandardContext启动时使用
            for (Map.Entry<ServletContainerInitializer,
                    Set<Class<?>>> entry :
                        initializerClassMap.entrySet()) {
                if (entry.getValue().isEmpty()) {
                    context.addServletContainerInitializer(
                            entry.getKey(), null);
                } else {
                    context.addServletContainerInitializer(
                            entry.getKey(), entry.getValue());
                }
            }
        }
    }


    protected void processClasses(WebXml webXml, Set<WebXml> orderedFragments) {
        // Step 4. Process /WEB-INF/classes for annotations and
        // @HandlesTypes matches
        //javaClassCache: key = className(全类限定名), value = {父类，接口}
        Map<String, JavaClassCacheEntry> javaClassCache;
        //根据是否并行来选择不同的缓存集合
        if (context.getParallelAnnotationScanning()) {
            javaClassCache = new ConcurrentHashMap<>();
        } else {
            javaClassCache = new HashMap<>();
        }

        if (ok) {
            /**
             * 获取项目/WEB-INF/classes下所有资源
             */
            WebResource[] webResources =
                    context.getResources().listResources("/WEB-INF/classes");
            //正常情况下webResource集合包含WEB-INF\classes\com，log4j.properties，spring-context.xml等资源信息
            for (WebResource webResource : webResources) {
                // Skip the META-INF directory from any JARs that have been
                // expanded in to WEB-INF/classes (sometimes IDEs do this).
                //如果为META-INF目录则跳过
                if ("META-INF".equals(webResource.getName())) {
                    continue;
                }
                // 当前.class是否包含注解，如果包含则进行合并处理  同时判断.class是否匹配@HandlesType注解
                processAnnotationsWebResource(webResource, webXml,
                        webXml.isMetadataComplete(), javaClassCache);
            }
        }

        // Step 5. Process JARs for annotations and
        // @HandlesTypes matches - only need to process those fragments we
        // are going to use (remember orderedFragments includes any
        // container fragments)
        // 步骤 5. 处理注解和@HandlesType匹配的JAR-只需要处理我们将要使用的片段（记住orderedFragments包括任何容器片段）
        // 处理web-fragment.xml文件及其所在包的.class文中配置的@WebServlet，@WebFilter和@WebListener注解的合并 同时判断.class是否匹配@HandlesType注解
        if (ok) {
            processAnnotations(
                    orderedFragments, webXml.isMetadataComplete(), javaClassCache);
        }

        // Cache, if used, is no longer required so clear it
        javaClassCache.clear();
    }

    //将web.xml的配置信息注入到StandardContext
    private void configureContext(WebXml webxml) {
        // As far as possible, process in alphabetical order so it is easy to
        // check everything is present 尽可能按照字母顺序进行处理，以便检查是否存在所有内容
        // Some validation depends on correct public ID
        //设置context容器的PublicID
        context.setPublicId(webxml.getPublicId());

        // Everything else in order
        //设置context容器的版本号
        context.setEffectiveMajorVersion(webxml.getMajorVersion());
        context.setEffectiveMinorVersion(webxml.getMinorVersion());
        //<context-param>标签
        for (Entry<String, String> entry : webxml.getContextParams().entrySet()) {
            //注意：这里的参数集合是添加到了StandardContext对象属性中 所有servlet皆可获取使用
            context.addParameter(entry.getKey(), entry.getValue());
        }
        //<deny-uncovered-http-methods>:用于指定对于<security-constraint>中未包含的htp-method是否允许访问。如不配置，这些http-method将可以被所有用户访问。
        context.setDenyUncoveredHttpMethods(
                webxml.getDenyUncoveredHttpMethods());
        //定义了WEB应用的名字
        context.setDisplayName(webxml.getDisplayName());
        context.setDistributable(webxml.isDistributable());
        //< ejb-local-ref>声明一个EJB的本地主目录的应用
        for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) {
            context.getNamingResources().addLocalEjb(ejbLocalRef);
        }
        //<ejb-ref>声明一个EJB的主目录的引用
        for (ContextEjb ejbRef : webxml.getEjbRefs().values()) {
            context.getNamingResources().addEjb(ejbRef);
        }
        //<env-entry>声明Web应用的环境项
        for (ContextEnvironment environment : webxml.getEnvEntries().values()) {
            context.getNamingResources().addEnvironment(environment);
        }
        //<error-page>用于配置Web应用访问异常时定向到的页面，支持HTTP响应码和异常类型两种形式
        for (ErrorPage errorPage : webxml.getErrorPages().values()) {
            context.addErrorPage(errorPage);
        }
        //<filter>用于配置Wb应用过滤器，用来过滤资源请求及响应。经常用于认证、日志、加密、数据转换等
        for (FilterDef filter : webxml.getFilters().values()) {
            if (filter.getAsyncSupported() == null) {
                filter.setAsyncSupported("false");
            }
            context.addFilterDef(filter);
        }
        // 设置FilterMapping，即Filter的URL映射
        for (FilterMap filterMap : webxml.getFilterMappings()) {
            context.addFilterMap(filterMap);
        }
        //设置jsp配置描述符
        context.setJspConfigDescriptor(webxml.getJspConfigDescriptor());
        //<listener>用于添加Web应用生命周期监听器，可以同时配置多个。监听器必须实现javax.servlet.ServletContextListener接口。
        // Web应用启动时会调用监听器的contextInitialized()方法，停止时调用其contextDestroyed()方法  （springMVC父容器）
        for (String listener : webxml.getListeners()) {
            context.addApplicationListener(listener);
        }
        //设置LocaleEncodingMappings
        // <locale-encoding-mapping-list>用于指定本地化与响应编码的映射关系，如果未显式地指明响应编码，服务器将按照当前本地化信息确定响应编码。
        for (Entry<String, String> entry :
                webxml.getLocaleEncodingMappings().entrySet()) {
            context.addLocaleEncodingMappingParameter(entry.getKey(),
                    entry.getValue());
        }
        // Prevents IAE
        //设置登录配置
        // <login-config>:指定Web应用的认证方式，当前支持BASIC、DIGEST、FORM、CLIENT-CERT这4种方式
        if (webxml.getLoginConfig() != null) {
            context.setLoginConfig(webxml.getLoginConfig());
        }
        //消息目标引用
        for (MessageDestinationRef mdr :
                webxml.getMessageDestinationRefs().values()) {
            context.getNamingResources().addMessageDestinationRef(mdr);
        }

        // messageDestinations were ignored in Tomcat 6, so ignore here
        // 元数据是否完整（注解），在之后的解析过程中会根据该标志来决定是否处理注解上的注解 如果为true，@WebServlet,@WebListener,
        // @WebFilter注解都已经忽略了，使用他们的类上的其他注解也没有处理的必须要
        context.setIgnoreAnnotations(webxml.isMetadataComplete());
        /*
        * <mime-mapping>
            <extension>doc</extension>
            <mime-type>application/msword</mime-type>
          </mime-mapping>*/
        // MIME（Multipurpose Internet Mail Extensions)即多用途互联网邮件扩展类型，用于设定某类型的扩展名文件将采用何种应用程序打开 如上，我们指定使用MS Word打开doc文档
        //<mime-mapping>用于为当前Web应用指定MIME映射
        for (Entry<String, String> entry :
                webxml.getMimeMappings().entrySet()) {
            context.addMimeMapping(entry.getKey(), entry.getValue());
        }
        context.setRequestCharacterEncoding(webxml.getRequestCharacterEncoding());
        // Name is just used for ordering
        //<resource-env-ref>声明与资源相关的一个管理对象
        for (ContextResourceEnvRef resource :
                webxml.getResourceEnvRefs().values()) {
            context.getNamingResources().addResourceEnvRef(resource);
        }
        //<resource-ref>配置和使用外部资源 比如添加JNDI
        for (ContextResource resource : webxml.getResourceRefs().values()) {
            context.getNamingResources().addResource(resource);
        }
        context.setResponseCharacterEncoding(webxml.getResponseCharacterEncoding());
        //设置权限
        boolean allAuthenticatedUsersIsAppRole =
                webxml.getSecurityRoles().contains(
                        SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS);
        //<security-constraint>:指定针对符合url-patterni的请求进行安全约束，粒度可以控制到具体的htp-method
        for (SecurityConstraint constraint : webxml.getSecurityConstraints()) {
            if (allAuthenticatedUsersIsAppRole) {
                constraint.treatAllAuthenticatedUsersAsApplicationRole();
            }
            context.addConstraint(constraint);
        }
        //设置安全角色SecurityRoles <security-role>:为Web应用添加一个角色
        for (String role : webxml.getSecurityRoles()) {
            context.addSecurityRole(role);
        }
        //添加服务引用
        for (ContextService service : webxml.getServiceRefs().values()) {
            context.getNamingResources().addService(service);
        }
        // 遍历所有的servlet,创建wrapper
        for (ServletDef servlet : webxml.getServlets().values()) {
            // 会创建一个StandardWrapper,并且将各种监听器设置上去
            Wrapper wrapper = context.createWrapper();
            // Description is ignored
            // Display name is ignored
            // Icons are ignored

            // jsp-file gets passed to the JSP Servlet as an init-param
            //load-on-startup:用于控制在Web应用启动时，Servlet的加载顺序。如果值小于0，在Web应用启动时，将不加载该Servlet
            if (servlet.getLoadOnStartup() != null) {
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            //enabled: 用于标识当前Servlet是否启用。如果为false,那么当前Servlet将不处理任何请求（返回404)
            if (servlet.getEnabled() != null) {
                wrapper.setEnabled(servlet.getEnabled().booleanValue());
            }
            //servlet-name:用于指定Servlet名称，该属性必须指定，而且在web.xml中必须唯一
            wrapper.setName(servlet.getServletName());
            //init-param:用于指定Servlet初始化参数
            Map<String,String> params = servlet.getParameterMap();
            for (Entry<String, String> entry : params.entrySet()) {
                //注意：这里的参数集合是添加到了StandardWrapper对象属性中 仅对当前servlet生效
                wrapper.addInitParameter(entry.getKey(), entry.getValue());
            }

            /*
            * <servlet>
                <servlet-name>myServlet</servlet-name>
                <servlet-class>org.myapp.servlet.MainServlet</servlet-class>
                <run-as>
                    <role-name>admin</role-name>
                </run-as>
                <security-role-ref>
                    <role-name>admin</role-name>
                    <role-link>manager</role-link>
                </security-role-ref>
              </servlet>
              * */
            //run-as: 用于标识访问当前Servlet所需的安全角色，用于进行安全控制.
            wrapper.setRunAs(servlet.getRunAs());
            // security-role-ref: 用于声明Web应用中使用的安全角色的引用，role-name指定代码中使用的角色名称，
            // role-link为role-name指向的服务器配置角色名称(tomcat-user.xml中配置的角色)。
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
            for (SecurityRoleRef roleRef : roleRefs) {
                wrapper.addSecurityReference(
                        roleRef.getName(), roleRef.getLink());
            }
            //servlet-class:用于指定Servlet类名，当Servlet指向单一的JSP页面时，并不需要指定servlet-class,此时只需要通过jsp-file属性指定JSP文件
            // 相对于Web应用根目录的路径即可  在步骤8中会对jsp-file属性不为空的servlet的该属性进行填充
            wrapper.setServletClass(servlet.getServletClass());
            //multipart-config: 用于指定Servlet上传文件请求配置，包括文件大小限制、请求大小限制以及文件写到磁盘的阈值（文件大小超过该值后将写到磁盘）
            MultipartDef multipartdef = servlet.getMultipartDef();
            if (multipartdef != null) {
                long maxFileSize = -1;
                long maxRequestSize = -1;
                int fileSizeThreshold = 0;

                if(null != multipartdef.getMaxFileSize()) {
                    maxFileSize = Long.parseLong(multipartdef.getMaxFileSize());
                }
                if(null != multipartdef.getMaxRequestSize()) {
                    maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize());
                }
                if(null != multipartdef.getFileSizeThreshold()) {
                    fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold());
                }

                wrapper.setMultipartConfigElement(new MultipartConfigElement(
                        multipartdef.getLocation(),
                        maxFileSize,
                        maxRequestSize,
                        fileSizeThreshold));
            }
            //<async-supported>:用于指定当前Servlet是否启用异步处理，取值为true或者false,默认为false。
            // 异步处理有利于在并发量较大时，及早释放Servlet资源，提升系统性能
            if (servlet.getAsyncSupported() != null) {
                wrapper.setAsyncSupported(
                        servlet.getAsyncSupported().booleanValue());
            }
            wrapper.setOverridable(servlet.isOverridable());
            // 将当前wrapper作为context的子容器
            // 在将wrapper作为context的子容器的时候，会执行wrapper组件的生命周期活动，调用wrapper的start方法，start方法中会判断当前wrapper的状态为new，
            // 所以会先执行init方法，再执行start方法,而start方法无非就是发布一些状态通知，绑定父子关系，StandardWrapperValve管道组件生命周期执行，
            // 将Wrapper组件状态修改为start
            context.addChild(wrapper);
        }
        // servlet-mapping：用于定义Servlet与URL表达式之间的映射关系（一个<servlet-mapping>可以同时配置多个url-pattern）
        // 根据<servlet-mapping>中定义的<servlet-name>属性找到对应的wrapper，然后把要匹配的url-pattern属性信息放入到wrapper中的mappings集合中，这样后续可以通过
        // 该属性集合获取到该wrapper需要匹配的路径信息
        for (Entry<String, String> entry :
                webxml.getServletMappings().entrySet()) {
            context.addServletMappingDecoded(entry.getKey(), entry.getValue());
        }

        /*
        * <session-config>
            <session-timeout>30</session-timeout>
            <cookie-config>
                <name>JSESSIONID</name>
                <domain>sample.myApp.com</domain>
                <path>/</path>
                <comment>The session cookie</comment>
                <http-only>true</http-only>
                <secure>true</secure>
                <max-age>3600</max-age>
            </cookie-config>
            <tracking-mode>COOKIE</tracking-mode>
          </session-config>
          * */
        //<session-config>用于配置Web应用会话，包括超时时间、Cookie配置以及会话追踪模式
        SessionConfig sessionConfig = webxml.getSessionConfig();
        if (sessionConfig != null) {
            if (sessionConfig.getSessionTimeout() != null) {
                //<session-timeout>用于配置会话超时时间，单位为分钟
                context.setSessionTimeout(
                        sessionConfig.getSessionTimeout().intValue());
            }
            //<cookie-config>用于配置会话追踪Cookie,需要关注的是http-only和secure,这两个属性主要用来控制Cookie的安全性。只有当会话追踪模式是Cookie时，
            // 该配置才会生效(Tomcat默认支持通过Cookie追踪会话)。此外，<domain>用于配置当前Cookiel所处的域，path用于配置Cookie所处的相对路径。
            SessionCookieConfig scc = context.getServletContext().getSessionCookieConfig();
            scc.setName(sessionConfig.getCookieName());
            Map<String,String> attributes = sessionConfig.getCookieAttributes();
            for (Map.Entry<String,String> attribute : attributes.entrySet()) {
                scc.setAttribute(attribute.getKey(), attribute.getValue());
            }
            //<tracking-mode>用于配置会话追踪模式，即通过何种方式从请求中获得会话标识（可配置多个)，Servlet规范3.`支持3种追踪模式：COOKIE、URL、SSL。
            if (sessionConfig.getSessionTrackingModes().size() > 0) {
                context.getServletContext().setSessionTrackingModes(
                        sessionConfig.getSessionTrackingModes());
            }
        }

        // Context doesn't use version directly
        //<welcome-file-list>用于指定Web应用的欢迎文件列表。当请求地址为Web应用根地址时，服务器会尝试在请求地址后加上欢迎文件并进行请求定向
        for (String welcomeFile : webxml.getWelcomeFiles()) {
            /*
             * The following will result in a welcome file of "" so don't add
             * that to the context
             * <welcome-file-list>
             *   <welcome-file/>
             * </welcome-file-list>
             */
            if (welcomeFile != null && welcomeFile.length() > 0) {
                context.addWelcomeFile(welcomeFile);
            }
        }

        // Do this last as it depends on servlets
        //设置jsp的属性组，其目的是给一定范围类的JSP文件设置一些特性（放在最后是因为依赖servlets）
        for (JspPropertyGroup jspPropertyGroup :
                webxml.getJspPropertyGroups()) {
            String jspServletName = context.findServletMapping("*.jsp");
            if (jspServletName == null) {
                jspServletName = "jsp";
            }
            if (context.findChild(jspServletName) != null) {
                // 根据urlPatterns添加mapper
                for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
                    // 添加mapper映射
                    context.addServletMappingDecoded(urlPattern, jspServletName, true);
                }
            } else {
                if(log.isDebugEnabled()) {
                    for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
                        log.debug("Skipping " + urlPattern + " , no servlet " +
                                jspServletName);
                    }
                }
            }
        }
        //<post-construct>添加初始方法
        for (Entry<String, String> entry :
                webxml.getPostConstructMethods().entrySet()) {
            context.addPostConstructMethod(entry.getKey(), entry.getValue());
        }
        //<pre-destroy>添加销毁方法
        for (Entry<String, String> entry :
            webxml.getPreDestroyMethods().entrySet()) {
            context.addPreDestroyMethod(entry.getKey(), entry.getValue());
        }
    }


    private WebXml getTomcatWebXmlFragment(WebXmlParser webXmlParser) {

        WebXml webXmlTomcatFragment = createWebXml();
        webXmlTomcatFragment.setOverridable(true);

        // Set to distributable else every app will be prevented from being
        // distributable when the Tomcat fragment is merged with the main
        // web.xml
        webXmlTomcatFragment.setDistributable(true);
        // When merging, the default welcome files are only used if the app has
        // not defined any welcomes files.
        webXmlTomcatFragment.setAlwaysAddWelcomeFiles(false);
        //将/WEB-INF/tomcat-web.xml配置文件转化为WebResource
        WebResource resource = context.getResources().getResource(Constants.TomcatWebXml);
        if (resource.isFile()) {
            try {
                InputSource source = new InputSource(resource.getURL().toURI().toString());
                source.setByteStream(resource.getInputStream());
                //对其进行解析封装到webXmlTomcatFragment对象中
                if (!webXmlParser.parseWebXml(source, webXmlTomcatFragment, false)) {
                    ok = false;
                }
            } catch (URISyntaxException e) {
                log.error(sm.getString("contextConfig.tomcatWebXmlError"), e);
            }
        }
        return webXmlTomcatFragment;
    }


    private WebXml getDefaultWebXmlFragment(WebXmlParser webXmlParser) {

        // Host should never be null
        //获取当前context容器的父级host
        Host host = (Host) context.getParent();
        //该host之前是否解析过conf/web.xml及web.xml.default
        DefaultWebXmlCacheEntry entry = hostWebXmlCache.get(host);
        //获取全局层面的配置文件流信息，默认为conf/web.xml  当然我们可以在<context>标签中定义defaultWebXml属性值(可以是相对路径也可以是绝对路径)
        InputSource globalWebXml = getGlobalWebXmlSource();
        //获取host层面的配置文件流信息 web.xml.default
        InputSource hostWebXml = getHostWebXmlSource();

        long globalTimeStamp = 0;
        long hostTimeStamp = 0;

        if (globalWebXml != null) {
            URLConnection uc = null;
            try {
                URL url = new URL(globalWebXml.getSystemId());
                uc = url.openConnection();
                //初始化当前全局配置文件上次修改的时间戳
                globalTimeStamp = uc.getLastModified();
            } catch (IOException e) {
                globalTimeStamp = -1;
            } finally {
                if (uc != null) {
                    try {
                        uc.getInputStream().close();
                    } catch (IOException e) {
                        ExceptionUtils.handleThrowable(e);
                        globalTimeStamp = -1;
                    }
                }
            }
        }

        if (hostWebXml != null) {
            URLConnection uc = null;
            try {
                URL url = new URL(hostWebXml.getSystemId());
                uc = url.openConnection();
                //初始化当前host层面配置文件上次修改的时间戳
                hostTimeStamp = uc.getLastModified();
            } catch (IOException e) {
                hostTimeStamp = -1;
            } finally {
                if (uc != null) {
                    try {
                        uc.getInputStream().close();
                    } catch (IOException e) {
                        ExceptionUtils.handleThrowable(e);
                        hostTimeStamp = -1;
                    }
                }
            }
        }
        // 如果发现找出来的webxml文件和缓存中的webxml文件最近的修改时间相等，就代表没有修改过，那么直接返回缓存中的WebXml对象
        if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp &&
                entry.getHostTimeStamp() == hostTimeStamp) {
            InputSourceUtil.close(globalWebXml);
            InputSourceUtil.close(hostWebXml);
            return entry.getWebXml();
        }

        // Parsing global web.xml is relatively expensive. Use a sync block to
        // make sure it only happens once. Use the pipeline since a lock will
        // already be held on the host by another thread
        //解析全局web.xml相对来说比较耗费性能。使用同步块确保它只发生一次。使用管道进行加锁
        synchronized (host.getPipeline()) {
            // 再一次判断是不是可以直接返回缓存中的WebXML对象，这里使用的是双重判断检查
            entry = hostWebXmlCache.get(host);
            if (entry != null && entry.getGlobalTimeStamp() == globalTimeStamp &&
                    entry.getHostTimeStamp() == hostTimeStamp) {
                return entry.getWebXml();
            }
            //创建WebXml对象，用来接受配置文件信息
            WebXml webXmlDefaultFragment = createWebXml();
            //设置属性可重写覆盖
            webXmlDefaultFragment.setOverridable(true);
            // Set to distributable else every app will be prevented from being
            // distributable when the default fragment is merged with the main
            // web.xml
            // 设置为可分发，否则当默认片段与主web.xml合并时，将阻止每个应用程序可分发
            webXmlDefaultFragment.setDistributable(true);
            // When merging, the default welcome files are only used if the app has
            // not defined any welcomes files.
            // 合并时，只有当应用程序未定义任何欢迎文件时，才会使用默认的欢迎文件 <welcome-file-list>
            webXmlDefaultFragment.setAlwaysAddWelcomeFiles(false);

            // Parse global web.xml if present
            if (globalWebXml == null) {
                // This is unusual enough to log
                log.info(sm.getString("contextConfig.defaultMissing"));
            } else {
                //全局配置文件conf/web.xml解析
                if (!webXmlParser.parseWebXml(
                        globalWebXml, webXmlDefaultFragment, false)) {
                    ok = false;
                }
            }

            // Parse host level web.xml if present
            // Additive apart from welcome pages
            //将当前的WebXml对象中的欢迎页替换属性设置为true，也就是说如果两个配置文件均设置了欢迎页，那么以host为主， 如果该值为false的话意味着欢迎页为两者的合并列表
            webXmlDefaultFragment.setReplaceWelcomeFiles(true);
            //host层面配置文件web.xml.default解析
            if (!webXmlParser.parseWebXml(
                    hostWebXml, webXmlDefaultFragment, false)) {
                ok = false;
            }

            // Don't update the cache if an error occurs
            //如果出现错误则不更新缓存
            if (globalTimeStamp != -1 && hostTimeStamp != -1) {
                entry = new DefaultWebXmlCacheEntry(webXmlDefaultFragment,
                        globalTimeStamp, hostTimeStamp);
                hostWebXmlCache.put(host, entry);
                // Add a Lifecycle listener to the Host that will remove it from
                // the hostWebXmlCache once the Host is destroyed
                //增加了一个事件监听，当host销毁时会触发，将hostWebXmlCache缓存进行清除释放，避免内存泄漏
                host.addLifecycleListener(new HostWebXmlCacheCleaner());
            }

            return webXmlDefaultFragment;
        }
    }


    private void convertJsps(WebXml webXml) {
        // 用来存储<init-param>标签属性值
        Map<String,String> jspInitParams;
        //获取名称为jsp的servlet
        ServletDef jspServlet = webXml.getServlets().get("jsp");
        //如果为null，则从context中获取其子节点中是否包含
        if (jspServlet == null) {
            jspInitParams = new HashMap<>();
            Wrapper w = (Wrapper) context.findChild("jsp");
            if (w != null) {
                String[] params = w.findInitParameters();
                for (String param : params) {
                    jspInitParams.put(param, w.findInitParameter(param));
                }
            }
        } else {
            jspInitParams = jspServlet.getParameterMap();
        }
        for (ServletDef servletDef: webXml.getServlets().values()) {
            //servlet标签中的<jsp-file>是否不为null
            if (servletDef.getJspFile() != null) {
                convertJsp(servletDef, jspInitParams);
            }
        }
    }

    private void convertJsp(ServletDef servletDef,
            Map<String,String> jspInitParams) {
        //将当前的ServletDef中的servletClass属性指向固定的实现类：org.apache.jasper.servlet.JspServlet
        servletDef.setServletClass(org.apache.catalina.core.Constants.JSP_SERVLET_CLASS);
        String jspFile = servletDef.getJspFile();
        //对jspFile的格式进行调整
        if ((jspFile != null) && !jspFile.startsWith("/")) {
            if (context.isServlet22()) {
                if(log.isDebugEnabled()) {
                    log.debug(sm.getString("contextConfig.jspFile.warning",
                                       jspFile));
                }
                jspFile = "/" + jspFile;
            } else {
                throw new IllegalArgumentException
                    (sm.getString("contextConfig.jspFile.error", jspFile));
            }
        }
        //将jspFile的值存入到InitParams中
        servletDef.getParameterMap().put("jspFile", jspFile);
        //jspFile属性置空
        servletDef.setJspFile(null);
        for (Map.Entry<String, String> initParam: jspInitParams.entrySet()) {
            servletDef.addInitParameter(initParam.getKey(), initParam.getValue());
        }
    }

    protected WebXml createWebXml() {
        return new WebXml();
    }

    /**
     * Scan JARs for ServletContainerInitializer implementations.
     * 扫描所有jars获取ServletContainerInitializer接口的实现类
     */
    protected void processServletContainerInitializers() {

        List<ServletContainerInitializer> detectedScis;
        try {
            WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
            // 寻找jar包中的META-INF/services/jakarta.servlet.ServletContainerInitializer文件
            // 以及Web目录下的META-INF/services/javax.servlet.ServletContainerInitializer 通过反射完成这些ServletContainerInitializer的实例化，
            // 然后再设置到 Context容器中， 最后，Context容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法
            // 该文件中记录了当前jar包中所有的ServletContainerInitializer实现类
            // 目前仅有spring-web.jar中存在其实现类为SpringServletContainerInitializer  springMVC spi机制
            detectedScis = loader.load(ServletContainerInitializer.class);
        } catch (IOException e) {
            log.error(sm.getString(
                    "contextConfig.servletContainerInitializerFail",
                    context.getName()),
                e);
            ok = false;
            return;
        }
        //处理实现类的HandlesTypes注解
        for (ServletContainerInitializer sci : detectedScis) {
            //将sci实现类存入到initializerClassMap集合中，注意其value此时为空set,在之后解析的过程中会将该sci中@hanlesTypes对应类型的实现类存入到该空set中，作为
            //其onstartup方法的参数  假如@HandlesTypes(value = {A.class,B.class})  此时该set集合装载的是A,B实现类的集合
            initializerClassMap.put(sci, new HashSet<>());

            HandlesTypes ht;
            try {
                ht = sci.getClass().getAnnotation(HandlesTypes.class);
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.info(sm.getString("contextConfig.sci.debug",
                            sci.getClass().getName()),
                            e);
                } else {
                    log.info(sm.getString("contextConfig.sci.info",
                            sci.getClass().getName()));
                }
                continue;
            }
            // 如果ServletContainerInitializer没有HandlesTypes注解，则忽略掉
            if (ht == null) {
                continue;
            }
            // 获取HandlesTypes注解中的value值 形如：@HandlesTypes(value = {AnnoContainerInitializer.class,Filter.class})
            // 注解的value值，只能是父类，接口，或注解，而不能写成实现类本身  假如Filter.class定义的为注解 那么会获取含有该注解的所有类作为参数传入
            // spring-web:interface org.springframework.web.WebApplicationInitializer 在接下来的4,5步骤中会通过bcel的方式来扫描我们所有的
            // .class文件是否实现了该接口
            Class<?>[] types = ht.value();
            if (types == null) {
                continue;
            }
            //判断注解中的值是否为注解  spring-web中传入的为接口
            for (Class<?> type : types) {
                if (type.isAnnotation()) {
                    // @HandlerTypes 的values中有注解类型
                    handlesTypesAnnotations = true;
                } else {
                    // @HandlerTypes 的values中有无注解类型
                    handlesTypesNonAnnotations = true;
                }
                //typeInitializerMap存储的是ServletContainerInitializer对象有@HandlesTypes注解，并且以HandlesTypes的values为key，而ServletContainerInitializer为value，
                //如MyServletContainerInitializer和YouServletContainerInitializer都配置了@HandlesTypes(value = {Filter.class}) 注解，因此typeInitializerMap中会存储key为Filter.class，值为拥有 MyServletContainerInitializer和YouServletContainerInitializer两个对象的HashSet。
                typeInitializerMap.computeIfAbsent(type, k -> new HashSet<>()).add(sci);
            }
        }
    }


    /**
     * Scan JARs that contain web-fragment.xml files that will be used to
     * configure this application to see if they also contain static resources.
     * If static resources are found, add them to the context. Resources are
     * added in web-fragment.xml priority order.
     * 扫描包含将用于配置此应用程序的web-fragment.xml文件的JAR，以查看它们是否也包含静态资源。
     * 如果找到静态资源，请将它们添加到上下文中。资源以web-fragment.xml优先级顺序添加
     * @param fragments The set of fragments that will be scanned for
     *  static resources
     */
    protected void processResourceJARs(Set<WebXml> fragments) {
        for (WebXml fragment : fragments) {
            URL url = fragment.getURL();
            try {
                if ("jar".equals(url.getProtocol()) || url.toString().endsWith(".jar")) {
                    try (Jar jar = JarFactory.newInstance(url)) {
                        jar.nextEntry();
                        String entryName = jar.getEntryName();
                        while (entryName != null) {
                            if (entryName.startsWith("META-INF/resources/")) {
                                context.getResources().createWebResourceSet(
                                        WebResourceRoot.ResourceSetType.RESOURCE_JAR,
                                        "/", url, "/META-INF/resources");
                                break;
                            }
                            jar.nextEntry();
                            entryName = jar.getEntryName();
                        }
                    }
                } else if ("file".equals(url.getProtocol())) {
                    File file = new File(url.toURI());
                    File resources = new File(file, "META-INF/resources/");
                    if (resources.isDirectory()) {
                        context.getResources().createWebResourceSet(
                                WebResourceRoot.ResourceSetType.RESOURCE_JAR,
                                "/", resources.getAbsolutePath(), null, "/");
                    }
                }
            } catch (IOException | URISyntaxException e) {
                log.error(sm.getString("contextConfig.resourceJarFail", url,
                        context.getName()));
            }
        }
    }


    /**
     * Identify the default web.xml to be used and obtain an input source for
     * it.
     * @return an input source to the default web.xml
     */
    protected InputSource getGlobalWebXmlSource() {
        // Is a default web.xml specified for the Context?
        if (defaultWebXml == null && context instanceof StandardContext) {
            //<context>标签是否设置defaultWebXml属性值
            defaultWebXml = ((StandardContext) context).getDefaultWebXml();
        }
        // Set the default if we don't have any overrides
        if (defaultWebXml == null) {
            //如果标签中没有设置，获取默认值conf/web.xml
            getDefaultWebXml();
        }

        // Is it explicitly suppressed, e.g. in embedded environment?
        // 是否明确禁止，例如在内嵌环境中无conf/web.xml文件
        if (Constants.NoDefaultWebXml.equals(defaultWebXml)) {
            return null;
        }
        return getWebXmlSource(defaultWebXml, true);
    }


    /**
     * Identify the host web.xml to be used and obtain an input source for
     * it.
     * @return an input source to the default per host web.xml
     */
    protected InputSource getHostWebXmlSource() {
        //获取xmlBase路径信息 即 engine[name]/host[name]
        File hostConfigBase = getHostConfigBase();
        if (hostConfigBase == null) {
            return null;
        }

        return getWebXmlSource(hostConfigBase.getPath(), false);
    }

    /**
     * Identify the application web.xml to be used and obtain an input source
     * for it.
     * 确定要使用的应用程序web.xml并获取其输入源。
     * @return an input source to the context web.xml
     */
    protected InputSource getContextWebXmlSource() {
        InputStream stream = null;
        InputSource source = null;
        URL url = null;

        String altDDName = null;

        // Open the application web.xml file, if it exists
        // 获取应用上下文
        ServletContext servletContext = context.getServletContext();
        try {
            if (servletContext != null) {
                // 上下文中是否存储过部署描述符文件 默认为null，部署描述符是一个XML文件，用于配置Web应用程序的行为和设置。
                // 使用替代的部署描述符允许开发人员在不修改原始的部署描述符的情况下，通过提供另一个XML文件来配置和调整Web应用程序的行为。
                // 这种灵活性可以用于在不重新打包和部署应用程序的情况下，修改应用程序的配置。
                altDDName = (String)servletContext.getAttribute(Globals.ALT_DD_ATTR);
                if (altDDName != null) {
                    try {
                        stream = new FileInputStream(altDDName);
                        url = new File(altDDName).toURI().toURL();
                    } catch (FileNotFoundException e) {
                        log.error(sm.getString("contextConfig.altDDNotFound",
                                               altDDName));
                    } catch (MalformedURLException e) {
                        log.error(sm.getString("contextConfig.applicationUrl"));
                    }
                } else {
                    stream = servletContext.getResourceAsStream
                        (Constants.ApplicationWebXml);
                    try {
                        url = servletContext.getResource(
                                Constants.ApplicationWebXml);
                    } catch (MalformedURLException e) {
                        log.error(sm.getString("contextConfig.applicationUrl"));
                    }
                }
            }
            if (stream == null || url == null) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("contextConfig.applicationMissing") + " " + context);
                }
            } else {
                source = new InputSource(url.toExternalForm());
                source.setByteStream(stream);
            }
        } finally {
            if (source == null && stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }

        return source;
    }

    public String getConfigBasePath() {
        String path = null;
        if (context.getParent() instanceof Host) {
            Host host = (Host) context.getParent();
            if (host.getXmlBase() != null) {
                path = host.getXmlBase();
            } else {
                StringBuilder xmlDir = new StringBuilder("conf");
                Container parent = host.getParent();
                if (parent instanceof Engine) {
                    xmlDir.append('/');
                    xmlDir.append(parent.getName());
                }
                xmlDir.append('/');
                xmlDir.append(host.getName());
                path = xmlDir.toString();
            }
        }
        return path;
    }

    /**
     * Utility method to create an input source from the specified XML file.
     * @param filename  Name of the file (possibly with one or more leading path
     *                  segments) to read
     * @param global true if processing a shared resource, false if processing
     *        a host based resource
     * @return the input source
     */
    protected InputSource getWebXmlSource(String filename, boolean global) {
        ConfigurationSource.Resource webXmlResource = null;
        try {
            if (global) {
                if (Constants.DefaultWebXml.equals(filename)) {
                    //全局配置文件，并且为默认配置文件
                    webXmlResource = ConfigFileLoader.getSource().getSharedWebXml();
                } else {
                    //全局配置文件，但是指定了defaultWebXml属性值，根据指定值进行加载
                    webXmlResource = ConfigFileLoader.getSource().getResource(filename);
                }
            } else {
                //host层面的web.xml.default加载
                String hostWebXml = Container.getConfigPath(context, Constants.HostWebXml);
                webXmlResource = ConfigFileLoader.getSource().getResource(hostWebXml);
            }
        } catch (IOException e) {
            // Ignore if not found
            return null;
        }

        InputStream stream = null;
        InputSource source = null;

        try {
            //将目标resource转化为InputSource对象(SAX解析时参数类型)进行返回
            stream = webXmlResource.getInputStream();
            source = new InputSource(webXmlResource.getURI().toString());
            if (stream != null) {
                source.setByteStream(stream);
            }
        } catch (Exception e) {
            log.error(sm.getString("contextConfig.defaultError", filename, webXmlResource.getURI()), e);
        } finally {
            if (source == null && stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }

        return source;
    }


    /**
     * Scan /WEB-INF/lib for JARs and for each one found add it and any
     * /META-INF/web-fragment.xml to the resulting Map. web-fragment.xml files
     * will be parsed before being added to the map. Every JAR will be added and
     * <code>null</code> will be used if no web-fragment.xml was found. Any JARs
     * known not contain fragments will be skipped.
     * 注意：这里返回的fragments集合中封装了该应用需要的所有jar包选项 jar中存在web-fragment.xml则 value不为null 否则为null
     * @param application The main web.xml metadata
     * @param webXmlParser The parser to use to process the web.xml file
     * @return A map of JAR name to processed web fragment (if any)
     */
    protected Map<String,WebXml> processJarsForWebFragments(WebXml application,
            WebXmlParser webXmlParser) {
        // 获取用于扫描此上下文的Jar资源的JarScanner
        JarScanner jarScanner = context.getJarScanner();
        boolean delegate = false;
        if (context instanceof StandardContext) {
            delegate = ((StandardContext) context).getDelegate();
        }
        boolean parseRequired = true;
        //web.xml 中<absolute-ordering>标签属性值集合 对web-fragment进行排序
        Set<String> absoluteOrder = application.getAbsoluteOrdering();
        if (absoluteOrder != null && absoluteOrder.isEmpty() &&
                !context.getXmlValidation()) {
            // Skip parsing when there is an empty absolute ordering and
            // validation is not enabled
            parseRequired = false;
        }

        FragmentJarScannerCallback callback =
                new FragmentJarScannerCallback(webXmlParser, delegate, parseRequired);
        // 扫描/WEB-INF/lib下的所有jar包，循环遍历，最终调用FragmentJarScannerCallback中的scan方法，为每一个jar包创建一个WebXml对象，
        // 如果该jar包中包含web-fragment.xml配置文件，则解析相应的数据封装到WebXml对象中 如果不存在那么WebXml对象中的数据为null
        // 将最终的K-V值存入fragments集合中
        jarScanner.scan(JarScanType.PLUGGABILITY,
                context.getServletContext(), callback);

        if (!callback.isOk()) {
            ok = false;
        }
        return callback.getFragments();
    }

    protected void processAnnotations(Set<WebXml> fragments,
            boolean handlesTypesOnly, Map<String, JavaClassCacheEntry> javaClassCache) {
        //是否开启多线程来处理fragments集合数据，默认为false
        if (context.getParallelAnnotationScanning()) {
            processAnnotationsInParallel(fragments, handlesTypesOnly, javaClassCache);
        } else {
            //fragments中包含项目所依赖的所有jar
            for (WebXml fragment : fragments) {
                //循环遍历 解析   handlesTypesOnly为主web.xml中的metaData-complate属性值 默认为false
                scanWebXmlFragment(handlesTypesOnly, fragment, javaClassCache);
            }
        }
    }

    private void scanWebXmlFragment(boolean handlesTypesOnly, WebXml fragment, Map<String, JavaClassCacheEntry> javaClassCache) {

        // Only need to scan for @HandlesTypes matches if any of the
        // following are true:
        // - it has already been determined only @HandlesTypes is required
        //   (e.g. main web.xml has metadata-complete="true"
        // - this fragment is for a container JAR (Servlet 3.1 section 8.1)
        // - this fragment has metadata-complete="true"
        // 主web.xml的metadata-complete为true
        // 或jar包下的web-fragment.xml的metadata-complete属性为true
        // 或此片段用于容器JAR    htOnly的真假决定是否解析class中的注解信息，为false时进行解析  默认为false
        boolean htOnly = handlesTypesOnly || !fragment.getWebappJar() ||
                fragment.isMetadataComplete();
        //创建WebXml对象 封装jar包class中注解属性信息 再该方法最后再和片段进行合并 不过项目下的.class是直接合并
        WebXml annotations = new WebXml();
        // no impact on distributable
        annotations.setDistributable(true);
        //URL指向的当前fragment在磁盘中的位置 形如：file:/G:/tomcat/apache-tomcat-10.1.2-src/target/classes/(文件)  该目录是在扫描jar包时关联添加的为了指示目标文件在文件系统中的位置
        //file:/G:/tomcat/apache-tomcat-10.1.2-src/home/webapps/test/WEB-INF/lib/mysql-connector-java-5.1.30.jar(jar)
        URL url = fragment.getURL();
        processAnnotationsUrl(url, annotations, htOnly, javaClassCache);
        Set<WebXml> set = new HashSet<>();
        set.add(annotations);
        // Merge annotations into fragment - fragment takes priority
        //最终将web-fragment.xml的内容与jar包扫描.class文件的@WebServlet和@WebFilter及@WebListener得到的ServletDef和FilterDef和Listener合并在一起
        //注意：如URL为jar:file:/Users/quyixiao/gitlab/tomcat/webapps/servelet-test-1.0/WEB-INF/lib/logback-classic-1.2.3
        // .jar!/的WebXml只会与logback-classic-1.2.3.jar包内部的扫描到.class的注解合并，先自己包的web-fragment.xml与自己包扫描class得到的ServletDef, FilterDef 及Listener合并。
        // 在processAnnotations()方法中不会发生包与包之间的web-fragment之间合并。
        fragment.merge(set);
    }

    /**
     * Executable task to scan a segment for annotations. Each task does the
     * same work as the for loop inside processAnnotations();
     */
    private class AnnotationScanTask implements Runnable {
        private final WebXml fragment;
        private final boolean handlesTypesOnly;
        private Map<String, JavaClassCacheEntry> javaClassCache;

        private AnnotationScanTask(WebXml fragment, boolean handlesTypesOnly, Map<String, JavaClassCacheEntry> javaClassCache) {
            this.fragment = fragment;
            this.handlesTypesOnly = handlesTypesOnly;
            this.javaClassCache = javaClassCache;
        }

        @Override
        public void run() {
            scanWebXmlFragment(handlesTypesOnly, fragment, javaClassCache);
        }

    }

    /**
     * Parallelized version of processAnnotationsInParallel(). Constructs tasks,
     * submits them as they're created, then waits for completion.
     *
     * @param fragments        Set of parallelizable scans
     * @param handlesTypesOnly Important parameter for the underlying scan
     * @param javaClassCache The class cache
     */
    protected void processAnnotationsInParallel(Set<WebXml> fragments, boolean handlesTypesOnly,
                                                Map<String, JavaClassCacheEntry> javaClassCache) {
        Server s = getServer();
        ExecutorService pool = null;
        //得到server初始化时定义的ScheduledThreadPoolExecutor线程池
        pool = s.getUtilityExecutor();
        List<Future<?>> futures = new ArrayList<>(fragments.size());
        for (WebXml fragment : fragments) {
            //执行该对象中的run方法
            Runnable task = new AnnotationScanTask(fragment, handlesTypesOnly, javaClassCache);
            futures.add(pool.submit(task));
        }
        try {
            for (Future<?> future : futures) {
                future.get();
            }
        } catch (Exception e) {
            throw new RuntimeException(sm.getString("contextConfig.processAnnotationsInParallelFailure"), e);
        }
    }

    protected void processAnnotationsWebResource(WebResource webResource,
            WebXml fragment, boolean handlesTypesOnly,
            Map<String,JavaClassCacheEntry> javaClassCache) {
        //如果还是文件夹，递归遍历
        if (webResource.isDirectory()) {
            WebResource[] webResources =
                    webResource.getWebResourceRoot().listResources(
                            webResource.getWebappPath());
            if (webResources.length > 0) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString(
                            "contextConfig.processAnnotationsWebDir.debug",
                            webResource.getURL()));
                }
                for (WebResource r : webResources) {
                    //递归
                    processAnnotationsWebResource(r, fragment, handlesTypesOnly, javaClassCache);
                }
            }
        //如果是一个文件，并且文件名以.class结尾
        } else if (webResource.isFile() &&
                webResource.getName().endsWith(".class")) {
            //拿到他的输入流
            try (InputStream is = webResource.getInputStream()) {
                //处理这个Class
                processAnnotationsStream(is, fragment, handlesTypesOnly, javaClassCache);
            } catch (IOException | ClassFormatException e) {
                log.error(sm.getString("contextConfig.inputStreamWebResource",
                        webResource.getWebappPath()),e);
            }
        }
    }


    protected void processAnnotationsUrl(URL url, WebXml fragment,
            boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache) {
        if (url == null) {
            // Nothing to do.
            return;
        } else if ("jar".equals(url.getProtocol()) || url.toString().endsWith(".jar")) {
            //处理jar包
            processAnnotationsJar(url, fragment, handlesTypesOnly, javaClassCache);
        } else if ("file".equals(url.getProtocol())) {
            try {
                //文件或文件夹的处理
                processAnnotationsFile(
                        new File(url.toURI()), fragment, handlesTypesOnly, javaClassCache);
            } catch (URISyntaxException e) {
                log.error(sm.getString("contextConfig.fileUrl", url), e);
            }
        } else {
            log.error(sm.getString("contextConfig.unknownUrlProtocol",
                    url.getProtocol(), url));
        }

    }


    protected void processAnnotationsJar(URL url, WebXml fragment,
            boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache) {

        try (Jar jar = JarFactory.newInstance(url)) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString(
                        "contextConfig.processAnnotationsJar.debug", url));
            }

            jar.nextEntry();
            String entryName = jar.getEntryName();
            //按照目录层级遍历整个jar包
            while (entryName != null) {
                if (entryName.endsWith(".class")) {
                    try (InputStream is = jar.getEntryInputStream()) {
                        //jar包中的.class处理 判断其是否符合@HandType注解的条件  该类上是否含有@webServlet等注解信息
                        processAnnotationsStream(is, fragment, handlesTypesOnly, javaClassCache);
                    } catch (IOException | ClassFormatException e) {
                        log.error(sm.getString("contextConfig.inputStreamJar",
                                entryName, url),e);
                    }
                }
                jar.nextEntry();
                entryName = jar.getEntryName();
            }
        } catch (IOException e) {
            log.error(sm.getString("contextConfig.jarFile", url), e);
        }
    }


    protected void processAnnotationsFile(File file, WebXml fragment,
            boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache) {
        //如果是文件夹，那么就递归获取其内部文件
        if (file.isDirectory()) {
            // Returns null if directory is not readable
            String[] dirs = file.list();
            if (dirs != null) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString(
                            "contextConfig.processAnnotationsDir.debug", file));
                }
                for (String dir : dirs) {
                    processAnnotationsFile(
                            new File(file,dir), fragment, handlesTypesOnly, javaClassCache);
                }
            }
        } else if (file.getName().endsWith(".class") && file.canRead()) {
            try (FileInputStream fis = new FileInputStream(file)) {
                //找到对应的.class文件进行处理
                processAnnotationsStream(fis, fragment, handlesTypesOnly, javaClassCache);
            } catch (IOException | ClassFormatException e) {
                log.error(sm.getString("contextConfig.inputStreamFile",
                        file.getAbsolutePath()),e);
            }
        }
    }

    // tomcat不会预先把该class加载到jvm中，而是通过解析字节码文件，获取对应类的一些信息，比如注解、实现的接口等
    // 如果我们直接使用java提供的Class对象，只需两步就能完成类型判断：Class.forname(...) 进行加载、Class.isAssignableFrom(...) 进行判断，
    // tomcat为何要搞了个ClassParser，其主要目的，应该是避免提前进行类加载，使用Class对象，必须要先加载类，而这样操作，就把所有的类都加载了，
    // 有些类在整个jvm生命都可能用不到，因此造成了额外的内存浪费。
    // 为避免类的提前加载，tomcat自己实现了jvm规范，搞了个ClassParser与JavaClass,只需要读文件，解析文件即可,而不是需要使用类加载器加载，
    // 而且，因为如果使用类加载器，则类加载器里面的静态代码块，等等就会被执行
    protected void processAnnotationsStream(InputStream is, WebXml fragment,
            boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache)
            throws ClassFormatException, IOException {
        //创建ClassParser对象来解析class文件对应的输入流，is就是class文件对应的输入流
        ClassParser parser = new ClassParser(is);
        //解析成JavaClass对象来封装class相关的信息
        JavaClass clazz = parser.parse();
        //检查类型，判断是否符合 `@HandlesTypes` 指定的类型
        checkHandlesTypes(clazz, javaClassCache);
        // 如果handlesTypesOnly为true，执行到这里就结束了  metadata-complete="true"，会在启动时不扫描注解（annotation）。
        // 如果不扫描注解的话，用注解进行的配置就无法生效，例如：@WebServlet,@WebFilter,@WebListener
        // web.xml: handlesTypesOnly=metadataComplete
        // web-fragment.xml: handlesTypesOnly=web.isMetadataComplete() || !fragment.getWebappJar() || fragment.isMetadataComplete()
        if (handlesTypesOnly) {
            return;
        }
        //@WebServlet、@WebFilter、@WebListener的处理,如果存在将对应的类添加到WebXml对象(主web.xml或者web-fragment.xml)属性中
        processClass(fragment, clazz);
    }

    //此时WebXml对象一定存在，如果项目存在web.xml文件，此时该对象的中的某些属性已经有值，将注解中的属性值进行合并
    //如果项目不存在web.xml 那么此处就是将注解的属性值直接填充到该对象中
    protected void processClass(WebXml fragment, JavaClass clazz) {
        //获取类中所有注解
        AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
        if (annotationsEntries != null) {
            String className = clazz.getClassName();
            //遍历类中的注解
            for (AnnotationEntry ae : annotationsEntries) {
                String type = ae.getAnnotationType();
                //@WebServlet：用来标记servlet类，标记了该注解的类会被注册为Servlet
                if ("Ljakarta/servlet/annotation/WebServlet;".equals(type)) {
                    //servlet：WebXml#servlets，类型为 Map<String,ServletDef>
                    processAnnotationWebServlet(className, ae, fragment);
                //@WebFilter：用来标记 filter 类，标记了该注解的类会被注册为 Filter
                }else if ("Ljakarta/servlet/annotation/WebFilter;".equals(type)) {
                    //filter：WebXml#filters，类型为 Map<String,FilterDef>
                    processAnnotationWebFilter(className, ae, fragment);
                //@WebListener：用来标记 listener 类，标记了该注解的类会被注册为 Listener
                }else if ("Ljakarta/servlet/annotation/WebListener;".equals(type)) {
                    //listener：WebXml#listeners，类型为 Set<String>
                    fragment.addListener(className);
                } else {
                    // Unknown annotation - ignore
                }
            }
        }
    }


    /**
     * For classes packaged with the web application, the class and each
     * super class needs to be checked for a match with {@link HandlesTypes} or
     * for an annotation that matches {@link HandlesTypes}.
     * 对于与Web应用程序打包在一起的类，需要检查该类和每个超类是否与@HandlesTypes指定的类相匹配，或是否与@HandlesTypes指定的注解相匹配
     * @param javaClass the class to check
     * @param javaClassCache a class cache
     */
    protected void checkHandlesTypes(JavaClass javaClass,
            Map<String,JavaClassCacheEntry> javaClassCache) {

        // Skip this if we can
        // 这个是步骤3里进行添加的，
        // typeInitializerMap -> key =  HandlesTypes.value里的class, value = ServletContainerInitializer实现集合
        if (typeInitializerMap.size() == 0) {
            return;
        }
        // 这里是这个类的可访问判断
        if ((javaClass.getAccessFlags() &
                org.apache.tomcat.util.bcel.Const.ACC_ANNOTATION) != 0) {
            // Skip annotations.
            return;
        }

        String className = javaClass.getClassName();

        Class<?> clazz = null;
        // 这个属性是步骤3进行设置的，在解析class后，判断不是注解，handlesTypesNonAnnotations=true
        if (handlesTypesNonAnnotations) {
            // This *might* be match for a HandlesType.
            // 这里比较简单，具体他会将该类的父类及父类的父类递归的找出来，然后放到javaClassCache里
            // javaClassCache: key = className, value = {父类，接口}
            populateJavaClassCache(className, javaClass, javaClassCache);
            JavaClassCacheEntry entry = javaClassCache.get(className);
            if (entry.getSciSet() == null) {
                // sciSet 指的是 Set<ServletContainerInitializer>
                try {
                    // 在上面的`populateJavaClassCache`只是将`/WEB-INF/classes`下的所有class加载了，并没有对sci进行设置
                    // 所以这里的步骤就是将`populateJavaClassCache`找出的class，设置对应的sci，如果是HandlesTypes.value里的类，那么就会有sci
                    // 同时，它将class作为key，从javaClassCache获取到父类，然后再通过父类，从typeInitializerMap获取到Set<ServletContainerInitializer>设置到entry里
                    // 要注意的是，这里判断null，是因为有可能在加载别的类的时候，加载过，这里的步骤属于懒加载，当第一次加载，会扫描，还有=null时扫描
                    populateSCIsForCacheEntry(entry, javaClassCache);
                } catch (StackOverflowError soe) {
                    throw new IllegalStateException(sm.getString(
                            "contextConfig.annotationsStackOverflow",
                            context.getName(),
                            classHierarchyToString(className, entry, javaClassCache)));
                }
            }
            //经过上面的匹配，如果此时该类中的sciSet属性不为空，说明其一定是@HandlesTypes中的实现类
            if (!entry.getSciSet().isEmpty()) {
                // Need to try and load the class
                //将该类进行加载生成对应的class对象  注意：这里涉及到tomcat的加载机制：WebappClassLoaderBase
                clazz = Introspection.loadClass(context, className);
                if (clazz == null) {
                    // Can't load the class so no point continuing
                    return;
                }
                // initializerClassMap 在步骤3中放入的，value是空的集合
                // 这里是匹配到需要当前class（HandlesTypes.value里的class的实现类）的sci，然后将当前class放到initializerClassMap
                for (ServletContainerInitializer sci : entry.getSciSet()) {
                    //需要注意的是这里的classes集合中存放的class对象可以是来源于项目中的class(执行步骤4时存放的)  也可以是项目依赖的jar中的class(步骤5)
                    Set<Class<?>> classes = initializerClassMap.computeIfAbsent(sci, k -> new HashSet<>());
                    classes.add(clazz);
                }
            }
        }
        // 处理@HandlersTypes中value值 是注解的情况
        if (handlesTypesAnnotations) {
            //获取当前类中的所有注解信息
            AnnotationEntry[] annotationEntries = javaClass.getAllAnnotationEntries();
            if (annotationEntries != null) {
                for (Map.Entry<Class<?>, Set<ServletContainerInitializer>> entry :
                        typeInitializerMap.entrySet()) {
                    //循环typeInitializerMap集合中key(handlesTypes的value值)是注解的元素
                    if (entry.getKey().isAnnotation()) {
                        String entryClassName = entry.getKey().getName();
                        //循环当前类中的注解是否和@handlesTypes中的注解一致
                        for (AnnotationEntry annotationEntry : annotationEntries) {
                            if (entryClassName.equals(
                                    getClassName(annotationEntry.getAnnotationType()))) {
                                if (clazz == null) {
                                    clazz = Introspection.loadClass(
                                            context, className);
                                    if (clazz == null) {
                                        // Can't load the class so no point
                                        // continuing
                                        return;
                                    }
                                }
                                for (ServletContainerInitializer sci : entry.getValue()) {
                                    initializerClassMap.get(sci).add(clazz);
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }
    }


    private String classHierarchyToString(String className,
            JavaClassCacheEntry entry, Map<String,JavaClassCacheEntry> javaClassCache) {
        JavaClassCacheEntry start = entry;
        StringBuilder msg = new StringBuilder(className);
        msg.append("->");

        String parentName = entry.getSuperclassName();
        JavaClassCacheEntry parent = javaClassCache.get(parentName);
        int count = 0;

        while (count < 100 && parent != null && parent != start) {
            msg.append(parentName);
            msg.append("->");

            count ++;
            parentName = parent.getSuperclassName();
            parent = javaClassCache.get(parentName);
        }

        msg.append(parentName);

        return msg.toString();
    }

    private void populateJavaClassCache(String className, JavaClass javaClass,
            Map<String,JavaClassCacheEntry> javaClassCache) {
        //说明该类已经缓存过，直接返回
        if (javaClassCache.containsKey(className)) {
            return;
        }

        // Add this class to the cache
        //将当前类添加到缓存中
        javaClassCache.put(className, new JavaClassCacheEntry(javaClass));
        //将该类的父类做为基础类 该方法会获取父类的javaClass对象，然后再递归调用当前方法
        populateJavaClassCache(javaClass.getSuperclassName(), javaClassCache);
        //处理该类实现的接口
        for (String interfaceName : javaClass.getInterfaceNames()) {
            populateJavaClassCache(interfaceName, javaClassCache);
        }
    }

    private void populateJavaClassCache(String className,
            Map<String,JavaClassCacheEntry> javaClassCache) {
        if (!javaClassCache.containsKey(className)) {
            // className：java.lang.Object 将其转化为java/lang/Object.class 以便加载流信息
            String name = className.replace('.', '/') + ".class";
            try (InputStream is = context.getLoader().getClassLoader().getResourceAsStream(name)) {
                if (is == null) {
                    return;
                }
                ClassParser parser = new ClassParser(is);
                JavaClass clazz = parser.parse();
                populateJavaClassCache(clazz.getClassName(), clazz, javaClassCache);
            } catch (ClassFormatException | IOException e) {
                log.debug(sm.getString("contextConfig.invalidSciHandlesTypes",
                        className), e);
            }
        }
    }
    // 只要当前类的父类或接口(【注意】不包含当前类自己)被XXXServletContainerInitializer的@HandlesTypes(value = {YYY.class}) 注解的value值配置，
    // 如YYYY.class， 因为扫描当前类会生成一个JavaClassCacheEntry对象，并存储于cacheEntry缓存中， 如果当前类的父类或接口或祖先类或祖先接口是YYY，
    // 则将XXXServletContainerInitializer对象设置到当前类的JavaClassCacheEntry的sciSet中。
    private void populateSCIsForCacheEntry(JavaClassCacheEntry cacheEntry,
            Map<String,JavaClassCacheEntry> javaClassCache) {
        Set<ServletContainerInitializer> result = new HashSet<>();

        // Super class
        String superClassName = cacheEntry.getSuperclassName();
        //根据父类获取其对应的JavaClassCacheEntry
        JavaClassCacheEntry superClassCacheEntry =
                javaClassCache.get(superClassName);

        // Avoid an infinite loop with java.lang.Object 避免无限循环（java.lang.Object）
        // 通过比较当前类的缓存条目cacheEntry和父类的缓存条目superClassCacheEntry来判断是否出现了无限循环的情况。如果两者相等，
        // 说明当前类的父类是java.lang.Object，而java.lang.Object本身就是所有类的父类，为避免无限循环，将当前类的SCI集合设为空集合，并返回。
        if (cacheEntry.equals(superClassCacheEntry)) {
            cacheEntry.setSciSet(EMPTY_SCI_SET);
            return;
        }

        // May be null of the class is not present or could not be loaded.
        if (superClassCacheEntry != null) {
            // 如果父类不为null 且没有设置过sciSet 则递归调用 继续去找其父类
            if (superClassCacheEntry.getSciSet() == null) {
                populateSCIsForCacheEntry(superClassCacheEntry, javaClassCache);
            }
            //如果设置过sciSet 则存入result集合中 代表当前类符合条件 superClassCacheEntry.getSciSet()返回的是父类继承过来的sci,并不包含父类本身的sci
            result.addAll(superClassCacheEntry.getSciSet());
        }
        //从顶级类开始判断，逐层回退，判断该类是否是typeInitializerMap中的key值 如果是则说明存在对应的sci集合 这里用来判断父类本身是否含有sci
        result.addAll(getSCIsForClass(superClassName));

        // Interfaces
        //同上面  处理当前类实现的接口
        for (String interfaceName : cacheEntry.getInterfaceNames()) {
            JavaClassCacheEntry interfaceEntry =
                    javaClassCache.get(interfaceName);
            // A null could mean that the class not present in application or
            // that there is nothing of interest. Either way, nothing to do here
            // so move along
            if (interfaceEntry != null) {
                if (interfaceEntry.getSciSet() == null) {
                    populateSCIsForCacheEntry(interfaceEntry, javaClassCache);
                }
                result.addAll(interfaceEntry.getSciSet());
            }
            result.addAll(getSCIsForClass(interfaceName));
        }
        //最终将找到的sci 填充到当前类的sciSet属性中  意味着该类是某个sci中@handlesTypes value中的实现类
        cacheEntry.setSciSet(result.isEmpty() ? EMPTY_SCI_SET : result);
    }

    private Set<ServletContainerInitializer> getSCIsForClass(String className) {
        for (Map.Entry<Class<?>, Set<ServletContainerInitializer>> entry :
                typeInitializerMap.entrySet()) {
            Class<?> clazz = entry.getKey();
            if (!clazz.isAnnotation()) {
                if (clazz.getName().equals(className)) {
                    return entry.getValue();
                }
            }
        }
        return EMPTY_SCI_SET;
    }

    private static final String getClassName(String internalForm) {
        if (!internalForm.startsWith("L")) {
            return internalForm;
        }

        // Assume starts with L, ends with ; and uses / rather than .
        return internalForm.substring(1,
                internalForm.length() - 1).replace('/', '.');
    }
    // 解析WebServlet注解中的属性并添加到servletDef中  如果在web.xml中已经存在该servlet那么属性信息以web.xml配置的为主 注解配置的次之
    // 如果web.xml中不存在那么直接创建一个新的servlet，然后添加到webxml对象中的servlet集合中
    protected void processAnnotationWebServlet(String className,
            AnnotationEntry ae, WebXml fragment) {
        String servletName = null;
        // must search for name s. Spec Servlet API 3.0 - 8.2.3.3.n.ii page 81
        //获取注解上的key-value值列表
        List<ElementValuePair> evps = ae.getElementValuePairs();
        // 遍历注解上配置的name:value对
        for (ElementValuePair evp : evps) {
            String name = evp.getNameString();
            if ("name".equals(name)) {
                servletName = evp.getValue().stringifyValue();
                break;
            }
        }
        // 如果没有配置name,那么servletName为类名
        if (servletName == null) {
            // classname is default servletName as annotation has no name!
            servletName = className;
        }
        // 查看该servletName是否在webxml中存在
        ServletDef servletDef = fragment.getServlets().get(servletName);

        boolean isWebXMLservletDef;
        // 如果没有在webxml中定义，那么就定义一个Servlet
        if (servletDef == null) {
            servletDef = new ServletDef();
            servletDef.setServletName(servletName);
            servletDef.setServletClass(className);
            isWebXMLservletDef = false;
        } else {
            isWebXMLservletDef = true;
        }

        boolean urlPatternsSet = false;
        // 可以配置多个urlPatterns
        String[] urlPatterns = null;

        // List<ElementValuePair> evps = ae.getElementValuePairs();
        for (ElementValuePair evp : evps) {
            String name = evp.getNameString();
            if ("value".equals(name) || "urlPatterns".equals(name)) {
                if (urlPatternsSet) {
                    throw new IllegalArgumentException(sm.getString(
                            "contextConfig.urlPatternValue", "WebServlet", className));
                }
                urlPatternsSet = true;
                urlPatterns = processAnnotationsStringArray(evp.getValue());
            } else if ("description".equals(name)) {
                // 判空的作用就是保证以web.xml中的属性为主
                if (servletDef.getDescription() == null) {
                    servletDef.setDescription(evp.getValue().stringifyValue());
                }
            } else if ("displayName".equals(name)) {
                if (servletDef.getDisplayName() == null) {
                    servletDef.setDisplayName(evp.getValue().stringifyValue());
                }
            } else if ("largeIcon".equals(name)) {
                if (servletDef.getLargeIcon() == null) {
                    servletDef.setLargeIcon(evp.getValue().stringifyValue());
                }
            } else if ("smallIcon".equals(name)) {
                if (servletDef.getSmallIcon() == null) {
                    servletDef.setSmallIcon(evp.getValue().stringifyValue());
                }
            } else if ("asyncSupported".equals(name)) {
                if (servletDef.getAsyncSupported() == null) {
                    servletDef.setAsyncSupported(evp.getValue()
                            .stringifyValue());
                }
            } else if ("loadOnStartup".equals(name)) {
                if (servletDef.getLoadOnStartup() == null) {
                    servletDef
                            .setLoadOnStartup(evp.getValue().stringifyValue());
                }
            } else if ("initParams".equals(name)) {
                // 初始化参数键值对
                Map<String, String> initParams = processAnnotationWebInitParams(evp
                        .getValue());
                if (isWebXMLservletDef) {
                    //如果该servlet在webxml中也定义了,将注解上定义的initparams和webxml中定义的initparams合并
                    Map<String, String> webXMLInitParams = servletDef
                            .getParameterMap();
                    for (Map.Entry<String, String> entry : initParams
                            .entrySet()) {
                        if (webXMLInitParams.get(entry.getKey()) == null) {
                            servletDef.addInitParameter(entry.getKey(), entry
                                    .getValue());
                        }
                    }
                } else {
                    for (Map.Entry<String, String> entry : initParams
                            .entrySet()) {
                        servletDef.addInitParameter(entry.getKey(), entry
                                .getValue());
                    }
                }
            }
        }
        if (!isWebXMLservletDef && urlPatterns != null) {
            // 将Servlet定义添加到fragment中
            fragment.addServlet(servletDef);
        }
        if (urlPatterns != null) {
            // 如果webxml中对当前servletname没有配置mapping关系
            if (!fragment.getServletMappings().containsValue(servletName)) {
                for (String urlPattern : urlPatterns) {
                    fragment.addServletMapping(urlPattern, servletName);
                }
            }
        }

    }

    /**
     * process filter annotation and merge with existing one!
     * 将注解中的属性保存到FilterDef中也是以web.xml中为主 解析过程同servlert
     * FIXME: refactoring method too long and has redundant subroutines with
     *        processAnnotationWebServlet!
     * @param className The filter class name
     * @param ae The filter annotation
     * @param fragment The corresponding fragment
     */
    protected void processAnnotationWebFilter(String className,
            AnnotationEntry ae, WebXml fragment) {
        String filterName = null;
        // must search for name s. Spec Servlet API 3.0 - 8.2.3.3.n.ii page 81
        List<ElementValuePair> evps = ae.getElementValuePairs();
        for (ElementValuePair evp : evps) {
            String name = evp.getNameString();
            if ("filterName".equals(name)) {
                filterName = evp.getValue().stringifyValue();
                break;
            }
        }
        if (filterName == null) {
            // classname is default filterName as annotation has no name!
            filterName = className;
        }
        FilterDef filterDef = fragment.getFilters().get(filterName);
        FilterMap filterMap = new FilterMap();

        boolean isWebXMLfilterDef;
        if (filterDef == null) {
            filterDef = new FilterDef();
            filterDef.setFilterName(filterName);
            filterDef.setFilterClass(className);
            isWebXMLfilterDef = false;
        } else {
            isWebXMLfilterDef = true;
        }

        boolean urlPatternsSet = false;
        boolean servletNamesSet = false;
        boolean dispatchTypesSet = false;
        String[] urlPatterns = null;

        for (ElementValuePair evp : evps) {
            String name = evp.getNameString();
            if ("value".equals(name) || "urlPatterns".equals(name)) {
                if (urlPatternsSet) {
                    throw new IllegalArgumentException(sm.getString(
                            "contextConfig.urlPatternValue", "WebFilter", className));
                }
                urlPatterns = processAnnotationsStringArray(evp.getValue());
                urlPatternsSet = urlPatterns.length > 0;
                for (String urlPattern : urlPatterns) {
                    // % decoded (if required) using UTF-8
                    filterMap.addURLPattern(urlPattern);
                }
            } else if ("servletNames".equals(name)) {
                String[] servletNames = processAnnotationsStringArray(evp
                        .getValue());
                servletNamesSet = servletNames.length > 0;
                for (String servletName : servletNames) {
                    filterMap.addServletName(servletName);
                }
            } else if ("dispatcherTypes".equals(name)) {
                String[] dispatcherTypes = processAnnotationsStringArray(evp
                        .getValue());
                dispatchTypesSet = dispatcherTypes.length > 0;
                for (String dispatcherType : dispatcherTypes) {
                    filterMap.setDispatcher(dispatcherType);
                }
            } else if ("description".equals(name)) {
                if (filterDef.getDescription() == null) {
                    filterDef.setDescription(evp.getValue().stringifyValue());
                }
            } else if ("displayName".equals(name)) {
                if (filterDef.getDisplayName() == null) {
                    filterDef.setDisplayName(evp.getValue().stringifyValue());
                }
            } else if ("largeIcon".equals(name)) {
                if (filterDef.getLargeIcon() == null) {
                    filterDef.setLargeIcon(evp.getValue().stringifyValue());
                }
            } else if ("smallIcon".equals(name)) {
                if (filterDef.getSmallIcon() == null) {
                    filterDef.setSmallIcon(evp.getValue().stringifyValue());
                }
            } else if ("asyncSupported".equals(name)) {
                if (filterDef.getAsyncSupported() == null) {
                    filterDef
                            .setAsyncSupported(evp.getValue().stringifyValue());
                }
            } else if ("initParams".equals(name)) {
                Map<String, String> initParams = processAnnotationWebInitParams(evp
                        .getValue());
                if (isWebXMLfilterDef) {
                    Map<String, String> webXMLInitParams = filterDef
                            .getParameterMap();
                    for (Map.Entry<String, String> entry : initParams
                            .entrySet()) {
                        if (webXMLInitParams.get(entry.getKey()) == null) {
                            filterDef.addInitParameter(entry.getKey(), entry
                                    .getValue());
                        }
                    }
                } else {
                    for (Map.Entry<String, String> entry : initParams
                            .entrySet()) {
                        filterDef.addInitParameter(entry.getKey(), entry
                                .getValue());
                    }
                }

            }
        }
        if (!isWebXMLfilterDef) {
            fragment.addFilter(filterDef);
            if (urlPatternsSet || servletNamesSet) {
                filterMap.setFilterName(filterName);
                fragment.addFilterMapping(filterMap);
            }
        }
        if (urlPatternsSet || dispatchTypesSet) {
            Set<FilterMap> fmap = fragment.getFilterMappings();
            FilterMap descMap = null;
            for (FilterMap map : fmap) {
                if (filterName.equals(map.getFilterName())) {
                    descMap = map;
                    break;
                }
            }
            if (descMap != null) {
                String[] urlsPatterns = descMap.getURLPatterns();
                if (urlPatternsSet
                        && (urlsPatterns == null || urlsPatterns.length == 0)) {
                    for (String urlPattern : filterMap.getURLPatterns()) {
                        // % decoded (if required) using UTF-8
                        descMap.addURLPattern(urlPattern);
                    }
                }
                String[] dispatcherNames = descMap.getDispatcherNames();
                if (dispatchTypesSet
                        && (dispatcherNames == null || dispatcherNames.length == 0)) {
                    for (String dis : filterMap.getDispatcherNames()) {
                        descMap.setDispatcher(dis);
                    }
                }
            }
        }

    }

    protected String[] processAnnotationsStringArray(ElementValue ev) {
        List<String> values = new ArrayList<>();
        if (ev instanceof ArrayElementValue) {
            ElementValue[] arrayValues =
                ((ArrayElementValue) ev).getElementValuesArray();
            for (ElementValue value : arrayValues) {
                values.add(value.stringifyValue());
            }
        } else {
            values.add(ev.stringifyValue());
        }
        return values.toArray(new String[0]);
    }

    protected Map<String,String> processAnnotationWebInitParams(
            ElementValue ev) {
        Map<String, String> result = new HashMap<>();
        if (ev instanceof ArrayElementValue) {
            ElementValue[] arrayValues =
                ((ArrayElementValue) ev).getElementValuesArray();
            for (ElementValue value : arrayValues) {
                if (value instanceof AnnotationElementValue) {
                    List<ElementValuePair> evps = ((AnnotationElementValue) value)
                            .getAnnotationEntry().getElementValuePairs();
                    String initParamName = null;
                    String initParamValue = null;
                    for (ElementValuePair evp : evps) {
                        if ("name".equals(evp.getNameString())) {
                            initParamName = evp.getValue().stringifyValue();
                        } else if ("value".equals(evp.getNameString())) {
                            initParamValue = evp.getValue().stringifyValue();
                        } else {
                            // Ignore
                        }
                    }
                    result.put(initParamName, initParamValue);
                }
            }
        }
        return result;
    }

    private static class DefaultWebXmlCacheEntry {
        private final WebXml webXml;
        private final long globalTimeStamp;
        private final long hostTimeStamp;

        public DefaultWebXmlCacheEntry(WebXml webXml, long globalTimeStamp,
                long hostTimeStamp) {
            this.webXml = webXml;
            this.globalTimeStamp = globalTimeStamp;
            this.hostTimeStamp = hostTimeStamp;
        }

        public WebXml getWebXml() {
            return webXml;
        }

        public long getGlobalTimeStamp() {
            return globalTimeStamp;
        }

        public long getHostTimeStamp() {
            return hostTimeStamp;
        }
    }

    private static class HostWebXmlCacheCleaner implements LifecycleListener {

        @Override
        public void lifecycleEvent(LifecycleEvent event) {

            if (Lifecycle.AFTER_DESTROY_EVENT.equals(event.getType())) {
                Host host = (Host) event.getSource();
                hostWebXmlCache.remove(host);
            }
        }
    }

    static class JavaClassCacheEntry {
        public final String superclassName;

        public final String[] interfaceNames;
        //该属性意味当前class是哪些ServletContainerInitializer实现类中的onStartup参数
        private Set<ServletContainerInitializer> sciSet = null;

        public JavaClassCacheEntry(JavaClass javaClass) {
            superclassName = javaClass.getSuperclassName();
            interfaceNames = javaClass.getInterfaceNames();
        }

        public String getSuperclassName() {
            return superclassName;
        }

        public String[] getInterfaceNames() {
            return interfaceNames;
        }

        public Set<ServletContainerInitializer> getSciSet() {
            return sciSet;
        }

        public void setSciSet(Set<ServletContainerInitializer> sciSet) {
            this.sciSet = sciSet;
        }
    }
}
